vue之购物车流程实现
vuex之购物流程实现购物车页面实现功能购物车页面(Cart.vue)详情页页面实现功能详情页页面(Detail.vue)Tabbar页面(Tabbar.vue)App页面(App.vue)Home 页面(Home.vue)router下的index.jsstore文件下index.jsstore文件下cart.jsstore文件下的common.jsutils文件下的request.js效果图
vuex之购物流程实现
以下项目中前端以vue框架、vuex状态管理 、vue-router 管理路由跳转、ul框架vant 为技术栈,后端以nodejs搭建服务器、mongodb数据库存放数据。
购物车页面实现功能
1、store管理的state中的bookInfo数据
computed
:{
bookInfo() {
return this.$store
.state
.cart
.bookInfo
;
},
}
2、点击单选和全选按钮,当全选时全部选中,当点击单选后,有一个没选中,则全选不选中,否则选中
methods
:{
checkboxAll
: {
get() {
return this.bookInfo
.every((item
) => item
.checked
);
},
set(val
) {
this.bookInfo
.map((item
) => {
item
.checked
= val
;
return item
;
});
},
},
}
3、计算总价格
computed
:{
totalPrice() {
return this.$store
.getters
.totalPrice
;
},
}
4、在购物车中对数据的删除、修改、清空
methods
: {
onClickLeft() {
this.$router
.push("/home");
},
removeItem(id
) {
this.$store
.commit("remove", id
);
},
changeQty(id
, qty
) {
this.$store
.commit("changeQty", { _id
: id
, qty
});
},
onSubmit() {},
clearCart() {
this.$store
.commit("clear");
},
},
5、点击商品图片时跳转到详情页,并把id传递过去,在页面渲染时这条数据的id能获取得到
methods
:{
gotoDetail(id
) {
this.$router
.push("/Detail/" + id
);
},
}
6、跳转路由时,隐藏和显示tabbar(因为下面显示需要不一样)
created() {
this.$store
.commit("displayTabbar", false);
console
.log( this.$store
.state
)
},
destroyed() {
this.$store
.commit("displayTabbar", true);
},
购物车页面(Cart.vue)
<template
>
<div
>
<!-- nav
-bar 导航栏
-->
<van
-nav
-bar title
="购物车" left
-text
="返回首页" left
-arrow @click
-left
="onClickLeft" />
<!-- 步骤条
-->
<van
-steps
:active
="active">
<van
-step
>买家下单
</van
-step
>
<van
-step
>商家接单
</van
-step
>
<van
-step
>买家提货
</van
-step
>
<van
-step
>交易完成
</van
-step
>
</van
-steps
>
<div
class="cardShow">
<!-- card 卡片展示
-->
<van
-card
:title
="item.name"
:thumb
="item.imgurl"
v
-for="item in bookInfo"
:key
="item.name"
@click
-thumb
="gotoDetail(item._id)"
class="cardSty"
>
<template #tags
class="tagSty">
<van
-checkbox v
-model
="item.checked" class="checkedOne"></van
-checkbox
>
</template
>
<template #footer
>
<van
-button type
="danger" plain size
="small" @click
.stop
="removeItem(item._id)">
<van
-icon size
="16px" name
="delete" />
</van
-button
>
</template
>
<template #bottom
>
<span
class="Nowprice">{{item
.price
}}</span
>
<van
-stepper v
-model
="item.qty" @change
="changeQty(item._id,$event)" />
</template
>
</van
-card
>
<div style
="padding:10px">
<van
-button plain type
="danger" size
="small" @click
="clearCart">清空购物车
</van
-button
>
</div
>
</div
>
<!-- submit
-bar 提交
-->
<van
-submit
-bar
:price
="totalPrice" button
-text
="提交订单" @submit
="onSubmit">
<van
-checkbox v
-model
="checkboxAll">全选
</van
-checkbox
>
<template #tip
>
你的收货地址不支持同城送
,
<span
>修改地址
</span
>
</template
>
</van
-submit
-bar
>
</div
>
</template
>
<script
>
import Vue
from "vue";
import { Card
, Step
, Steps
, SubmitBar
, Checkbox
} from "vant";
Vue
.use(Card
);
Vue
.use(Step
);
Vue
.use(Steps
);
Vue
.use(SubmitBar
);
Vue
.use(Checkbox
);
export default {
data() {
return {
active
: 0,
stepValue
: 1,
};
},
methods
: {
onClickLeft() {
this.$router
.push("/home");
},
gotoDetail(id
) {
this.$router
.push("/Detail/" + id
);
},
removeItem(id
) {
this.$store
.commit("remove", id
);
},
changeQty(id
, qty
) {
this.$store
.commit("changeQty", { _id
: id
, qty
});
},
onSubmit() {},
clearCart() {
this.$store
.commit("clear");
},
},
computed
: {
bookInfo() {
return this.$store
.state
.cart
.bookInfo
;
},
checkboxAll
: {
get() {
return this.bookInfo
.every((item
) => item
.checked
);
},
set(val
) {
this.bookInfo
.map((item
) => {
item
.checked
= val
;
return item
;
});
},
},
totalPrice() {
return this.$store
.getters
.totalPrice
;
},
},
created() {
this.$store
.commit("displayTabbar", false);
console
.log( this.$store
.state
)
},
destroyed() {
this.$store
.commit("displayTabbar", true);
},
};
</script
>
<style
>
.tagSty
{
margin
-right
: 20px
;
}
.checkedOne
{
position
: absolute
;
width
: 20px
;
padding
: 0;
left
: -120px
;
top
: 30px
;
}
.cardShow
{
margin
-bottom
: 90px
;
}
.cardSty
{
padding
-left
: 30px
;
}
.Nowprice
{
font
-size
: 18px
;
color
: #f10
;
}
.Nowprice
::before
{
content
: "¥";
}
</style
>
详情页页面实现功能
1、当首页点击商品后,路由跳转 到detail页面时,把id传过来到详情页,取得id后,发送请求,得到数据后,渲染数据到页面
created() {
const pid
= this.$route
.params
.id
;
this.getData(pid
);
this.recData();
},
methods
:{
async getData(id
) {
const {
data
: {
data
: { result
: bookInfo
},
},
} = await this.$request
.get("/books/" + id
);
this.bookInfo
= bookInfo
[0];
},
async recData() {
const {
data
: { data
: recList
},
} = await this.$request
.get("/books", {
params
: {
size
: 6,
},
});
this.recommond
= recList
.result
;
},
}
2、路由跳转到detail页面时,推荐数据的获取,通过发送请求后,渲染数据到页面
created() {
const pid
= this.$route
.params
.id
;
this.getData(pid
);
this.recData();
},
methods
:{
async getData(id
) {
const {
data
: {
data
: { result
: bookInfo
},
},
} = await this.$request
.get("/books/" + id
);
this.bookInfo
= bookInfo
[0];
},
async recData() {
const {
data
: { data
: recList
},
} = await this.$request
.get("/books", {
params
: {
size
: 6,
},
});
this.recommond
= recList
.result
;
},
}
3、点击推荐商品后,把数据重新在detail页面上渲染(路由守卫)
beforeRouteUpdate(to
, from, next
) {
if (to
.params
.id
!== from.params
.id
) {
this.getData(to
.params
.id
);
this.recData();
}
next();
},
methods
:{
gotoDetail(id
) {
this.$router
.push({
name
: "Detail",
params
: {
id
,
},
});
}
4、添加商品到购物车,点击加入图书,先判断当前商品是否已经存在购物车中,如果存在,就让数量加1,否则不存在,添加到购物车中
addBook() {
const { _id
} = this.bookInfo
;
const current
= this.cartlist
.filter((item
) => item
._id
=== _id
)[0];
if (current
) {
this.$store
.commit("changeQty", { _id
, qty
: current
.qty
+ 1 });
} else {
const goods
= {
...this.bookInfo
,
qty
: 1,
};
this.$store
.commit("add", goods
);
}
},
computed
: {
cartlist() {
return this.$store
.state
.cart
.bookInfo
;
},
},
5、立即购买,添加商品并跳转
methods
:{
buyNow() {
this.addBook();
this.$router
.push("/cart");
},
}
6、点击购物车按钮跳转到购物车页面
methods
:{
gotoCart(id
) {
this.$router
.push({
name
: "Cart",
params
: {
id
,
},
});
},
}
7、跳转路由时,隐藏和显示tabbar(因为下面的显示不一样)
mounted() {
this.$store
.commit('displayTabbar',false)
},
destroyed() {
this.$store
.commit('displayTabbar',true)
},
详情页页面(Detail.vue)
<template
>
<div
>
<!-- nav
-bar 导航栏
-->
<van
-nav
-bar title
="书籍信息" left
-text
="返回首页" left
-arrow @click
-left
="onClickLeft" />
<!-- 商品详情信息
-->
<div
class="bookInfo">
<van
-image
:src
="bookInfo.imgurl" class="bookImg" @click
="ImgPrev"></van
-image
>
<h1
class="bookName">书名:
{{bookInfo
.name
}}</h1
>
<h1
class="bookAuth">作者:
{{bookInfo
.auth
}}</h1
>
<p
class="bookIntro">简介:
{{bookInfo
.intro
}}</p
>
</div
>
<!-- Grid 宫格
-->
<van
-grid
:border
="false" :column
-num
="2" class="gridBox">
<van
-grid
-item
v
-for="item in recommond"
:key
="item.name"
class="gridItem"
@click
="gotoDetail(item._id)"
>
<van
-image
:src
="item.imgurl" />
<p
>{{item
.name
}}</p
>
</van
-grid
-item
>
</van
-grid
>
<!-- tabbar 购物车
-->
<van
-goods
-action
>
<van
-goods
-action
-icon icon
="chat-o" text
="客服" color
="#07c160" />
<van
-goods
-action
-icon icon
="cart-o" text
="书籍库" :badge
="cartlist.length" @click
="gotoCart" />
<van
-goods
-action
-icon icon
="star" text
="已收藏" color
="#ff5000" />
<van
-goods
-action
-button type
="warning" text
="加入图书" @click
="addBook" />
<van
-goods
-action
-button type
="danger" text
="立即购买" @click
="buyNow" />
</van
-goods
-action
>
</div
>
</template
>
<script
>
import Vue
from "vue";
import {
Card
,
GoodsAction
,
GoodsActionButton
,
GoodsActionIcon
,
Step
,
Steps
,
Grid
,
GridItem
,
ImagePreview
,
} from "vant";
Vue
.use(Card
);
Vue
.use(GoodsAction
);
Vue
.use(GoodsActionButton
);
Vue
.use(GoodsActionIcon
);
Vue
.use(Step
);
Vue
.use(Steps
);
Vue
.use(Grid
);
Vue
.use(GridItem
);
Vue
.use(ImagePreview
);
export default {
data() {
return {
active
: 0,
bookInfo
: {},
recommond
: {},
};
},
methods
: {
async getData(id
) {
const {
data
: {
data
: { result
: bookInfo
},
},
} = await this.$request
.get("/books/" + id
);
this.bookInfo
= bookInfo
[0];
},
async recData() {
const {
data
: { data
: recList
},
} = await this.$request
.get("/books", {
params
: {
size
: 6,
},
});
this.recommond
= recList
.result
;
},
gotoCart(id
) {
this.$router
.push({
name
: "Cart",
params
: {
id
,
},
});
},
addBook() {
const { _id
} = this.bookInfo
;
const current
= this.cartlist
.filter((item
) => item
._id
=== _id
)[0];
if (current
) {
this.$store
.commit("changeQty", { _id
, qty
: current
.qty
+ 1 });
} else {
const goods
= {
...this.bookInfo
,
qty
: 1,
};
this.$store
.commit("add", goods
);
}
},
gotoDetail(id
) {
this.$router
.push({
name
: "Detail",
params
: {
id
,
},
});
},
ImgPrev() {
console
.log(1);
ImagePreview({
images
: [this.bookInfo
.imgurl
],
closeable
: true,
});
},
onClickLeft() {
this.$router
.push("/home");
},
buyNow() {
this.addBook();
this.$router
.push("/cart");
},
},
computed
: {
cartlist() {
return this.$store
.state
.cart
.bookInfo
;
},
},
created() {
const pid
= this.$route
.params
.id
;
this.getData(pid
);
this.recData();
},
mounted() {
this.$store
.commit('displayTabbar',false)
},
destroyed() {
this.$store
.commit('displayTabbar',true)
},
beforeRouteUpdate(to
, from, next
) {
if (to
.params
.id
!== from.params
.id
) {
this.getData(to
.params
.id
);
this.recData();
}
next();
},
};
</script
>
<style
>
.bookInfo
{
height
: 400px
;
}
.bookImg
{
width
: 150px
;
}
.bookName
{
font
-size
: 18px
;
}
.bookAuth
{
font
-size
: 14px
;
}
.bookIntro
{
font
-size
: 14px
;
height
: 60px
;
text
-overflow
: -o
-ellipsis
-lastline
;
overflow
: hidden
;
text
-overflow
: ellipsis
;
display
: -webkit
-box
;
-webkit
-line
-clamp
: 3;
line
-clamp
: 3;
-webkit
-box
-orient
: vertical
;
}
</style
>
Tabbar页面(Tabbar.vue)
<template
>
<van
-tabbar route v
-show
="showTabbar">
<van
-tabbar
-item
:icon
="item.icon"
:to
="item.path"
v
-for="item in menu"
:key
="item.name"
:badge
="item.name==='cart'?cartLength:''"
>{{item
.text
}}</van
-tabbar
-item
>
</van
-tabbar
>
</template
>
<script
>
import Vue
from "vue";
import { Tabbar
, TabbarItem
} from "vant";
Vue
.use(Tabbar
);
Vue
.use(TabbarItem
);
export default {
data() {
return {
menu
: [
{
path
: "/home",
icon
: "wap-home-o",
text
: "首页",
name
:'home'
},
{
path
: "/cart",
icon
: "cart-circle-o",
text
: "购物车",
name
:'cart'
},
{
path
: "/discover",
icon
: "browsing-history-o",
text
: "发现",
name
:'descover'
},
{
path
: "/profile",
icon
: "user-circle-o",
text
: "我的",
name
:'profile'
},
],
};
},
computed
:{
cartLength(){
return this.$store
.state
.cart
.bookInfo
.length
},
showTabbar(){
return this.$store
.state
.common
.showTabbar
}
},
created(){
}
};
</script
>
<style
>
</style
>
App页面(App.vue)
<template
>
<div id
="app">
<router
-view
/>
<tab
-bar
></tab
-bar
>
</div
>
</template
>
<script
>
import Vue
from "vue";
import {
Button
,
Image
,
ImagePreview
,
NavBar
,
Tag
,
Radio
,
Icon
,
Stepper
,
} from "vant";
Vue
.use(Button
);
Vue
.use(Image
);
Vue
.use(ImagePreview
);
Vue
.use(NavBar
);
Vue
.use(Tag
);
Vue
.use(Radio
);
Vue
.use(Icon
);
Vue
.use(Stepper
);
import TabBar
from "./components/tabber/TabBar";
export default {
components
: {
TabBar
,
},
};
</script
>
<style lang
="scss">
</style
>
Home 页面(Home.vue)
<template
>
<div
>
<van
-nav
-bar title
="首页" />
<!-- swipe 轮播图
-->
<van
-swipe
class="my-swipe" :autoplay
="3000" indicator
-color
="white" @change
="onChange">
<van
-swipe
-item v
-for="(item,index) in books" :key
="index">
<img
:src
="item.imgurl" alt
/>
</van
-swipe
-item
>
<template #indicator
>
<div
class="custom-indicator">{{ current
+ 1 }}/{{page
}}</div
>
</template
>
</van
-swipe
>
<!-- Grid 宫格
-->
<van
-grid
:border
="false" :column
-num
="2" class="gridBox">
<van
-grid
-item
v
-for="item in showList"
:key
="item.name"
class="gridItem"
@click
="gotoDetail(item._id)"
>
<van
-image
:src
="item.imgurl" />
<p
>{{item
.name
}}</p
>
</van
-grid
-item
>
</van
-grid
>
</div
>
</template
>
<script
>
import Vue
from "vue";
import { Swipe
, SwipeItem
, Image
} from "vant";
import { Grid
, GridItem
} from "vant";
Vue
.use(Swipe
);
Vue
.use(SwipeItem
);
Vue
.use(Image
);
Vue
.use(Grid
);
Vue
.use(GridItem
);
export default {
name
: "Home",
components
: {},
data() {
return {
current
: 0,
page
: 0,
books
: [],
showList
: [],
};
},
methods
: {
onChange(index
) {
this.current
= index
;
},
async getData() {
const {
data
: { data
: books
},
} = await this.$request
.get("/books", {
params
: {
size
: 6,
},
});
this.books
= books
.result
;
this.page
= this.books
.length
;
},
async showData() {
const {
data
: { data
: showList
},
} = await this.$request
.get("/books", {
params
: {
size
: 10,
},
});
this.showList
= showList
.result
;
},
gotoDetail(id
) {
this.$router
.push({
name
: "Detail",
params
: {
id
,
},
});
},
},
created() {
this.getData();
this.showData();
},
};
</script
>
<style lang
="scss">
.my
-swipe
.van
-swipe
-item
{
color
: #fff
;
font
-size
: 20px
;
height
: 100px
;
line
-height
: 100px
;
text
-align
: center
;
background
-color
: #
39a9ed
;
}
.my
-swipe
.van
-swipe
-item img
{
width
: 120px
;
height
: 100px
;
}
.custom
-indicator
{
position
: absolute
;
right
: 5px
;
bottom
: 5px
;
padding
: 2px
5px
;
font
-size
: 12px
;
background
: rgba(0, 0, 0, 0.1);
}
</style
>
router下的index.js
import Vue
from 'vue'
import VueRouter
from 'vue-router'
import Home
from '../views/Home.vue'
Vue
.use(VueRouter
)
const routes
= [{
path
: '/',
redirect
: {
name
: 'Home'
}
},
{
path
: '/home',
name
: 'Home',
component
: Home
},
{
path
: '/discover',
name
: 'Discover',
component
: () => import('../views/Discover.vue')
},
{
path
: '/cart',
name
: 'Cart',
component
: () => import('../views/Cart.vue')
},
{
path
: '/detail/:id',
name
: 'Detail',
component
: () => import('../views/Detail.vue')
},
{
path
: '/profile',
name
: 'Profile',
component
: () => import('../views/Profile.vue')
},
{
path
: '/login',
name
: 'Login',
component
: () => import('../views/Login.vue')
},
{
path
: '/reg',
name
: 'Reg',
component
: () => import('../views/Reg.vue')
}
]
const router
= new VueRouter({
routes
})
const originalPush
= VueRouter
.prototype
.push
VueRouter
.prototype
.push = function push(location
) {
return originalPush
.call(this, location
).catch(err
=> err
)
}
export default router
store文件下index.js
import Vue
from 'vue'
import Vuex
from 'vuex'
import cart
from './cart'
import common
from './common'
Vue
.use(Vuex
)
export default new Vuex.Store({
modules
:{
cart
,
common
}
})
store文件下cart.js
import request
from '../utils/request'
import {Notify
} from 'vant'
const cart
= {
state
: {
bookInfo
: [{
_id
: "5f4f6b9dd08ac99a108eb027",
name
: "鼎定乾坤",
date
: "2020 08-19 11:28",
intro
: "玄黄缔造者……极致超脱之路,哪怕天难葬其身,地难灭其魂。亦难以跳出那个圈。星空崩塌,万族凋零。如何以一己之力逆转乾坤,颠倒阴阳。且看那一袭青衫,一柄长剑,一方圆鼎:脚踏修真,拳碎仙域,剑斩神界,鼎定至尊,极致超脱。书群,1141419286扣扣",
auth
: "浅山深水",
imgurl
: "imgbook1/f3aa24ea917f79a95b81ea86c91b4043.jpeg",
price
: 123.2,
qty
: 1,
checked
: false,
},
{
_id
: "5f4f6b9dd08ac99a108eb02a",
name
: "雷神传之雷神再世",
date
: "2020 08-18 22:47",
intro
: "以武侠小说的名义 ,说一段刻骨铭心的爱情故事 !",
auth
: "猛士七",
imgurl
: "imgbook1/ba83c1528c275b6c154ffd482d4541c3.jpeg",
price
: 12,
qty
: 1,
checked
: false,
},
{
_id
: "5f4f6b9dd08ac99a108eb030",
name
: "窃时之旅",
date
: "2020 08-18 11:30",
intro
: "意外获得时间之灵,由此开启了一段万界时间大盗的传奇。【北爱完,生逢完,越狱完,神盾进行中...】",
auth
: "周子曰不曰",
imgurl
: "imgbook1/e21e64a6f9b65cd51dc69823d80daa7c.jpeg",
price
: 144,
qty
: 1,
checked
: false,
},
],
},
getters
: {
totalPrice(state
) {
return state
.bookInfo
.reduce((prev
, item
) => {
return prev
+ item
.price
* item
.qty
}, 0) * 100
}
},
mutations
: {
add(state
, goods
) {
state
.bookInfo
.unshift(goods
)
},
changeQty(state
, {
_id
,
qty
}) {
state
.bookInfo
= state
.bookInfo
.map(item
=> {
if (item
._id
=== _id
) {
item
.qty
= qty
}
return item
})
},
remove(state
, _id
) {
state
.bookInfo
= state
.bookInfo
.filter(item
=> item
._id
!== _id
)
},
clear(state
) {
state
.bookInfo
= []
}
},
actions
: {},
modules
: {}
}
export default cart
store文件下的common.js
const common
= {
state
: {
showTabbar
: true
},
getters
: {
},
mutations
: {
displayTabbar(state
, payload
) {
state
.showTabbar
= payload
}
},
actions
: {
}
}
export default common
utils文件下的request.js
import axios
from 'axios'
const request
= axios
.create({
baseURL
: 'http://localhost:3000/api',
withCredentials
: true
})
export default request
效果图
首页页面 购物车页面 详情页页面