vue之购物车流程实现
vuex之购物流程实现
以下项目中前端以vue框架、vuex状态管理 、vue-router 管理路由跳转、ul框架vant 为技术栈,后端以nodejs搭建服务器、mongodb数据库存放数据。
购物车页面实现功能
1、store管理的state中的bookInfo数据
computed:{
// store管理的state中的bookInfo数据
bookInfo() {
return this.$store.state.cart.bookInfo;
},
}
2、点击单选和全选按钮,当全选时全部选中,当点击单选后,有一个没选中,则全选不选中,否则选中
methods:{
// 全选按钮 判断单选/全选商品的选中状态
checkboxAll: {
get() {
return this.bookInfo.every((item) => item.checked);
},
set(val) {
// console.log(val, 999);
this.bookInfo.map((item) => {
item.checked = val;
return item;
});
},
},
}
3、计算总价格
computed:{
// 总价格
totalPrice() {
return this.$store.getters.totalPrice;
},
}
4、在购物车中对数据的删除、修改、清空
methods: {
// 导航栏
onClickLeft() {
// Toast("返回");
this.$router.push("/home");
},
// 删除数据
removeItem(id) {
this.$store.commit("remove", id);
},
// 修改数量
changeQty(id, qty) {
this.$store.commit("changeQty", { _id: id, qty });
},
onSubmit() {},
// 清空商品
clearCart() {
// console.log(this.checkboxAll)
this.$store.commit("clear");
},
},
5、点击商品图片时跳转到详情页,并把id传递过去,在页面渲染时这条数据的id能获取得到
methods:{
// 跳转详情页
gotoDetail(id) {
this.$router.push("/Detail/" + id);
},
}
6、跳转路由时,隐藏和显示tabbar(因为下面显示需要不一样)
created() {
// 跳转路由时,隐藏下面tabbar
// this.$store.state.showMenu = false;
this.$store.commit("displayTabbar", false);
console.log( this.$store.state)
},
destroyed() {
// 跳转路由时,显示下面tabbar
// this.$store.state.showMenu = true;
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() {
// Toast("返回");
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() {
// console.log(this.checkboxAll)
this.$store.commit("clear");
},
},
computed: {
// store管理的state中的bookInfo数据
bookInfo() {
return this.$store.state.cart.bookInfo;
},
// 全选按钮 判断单选/全选商品的选中状态
checkboxAll: {
get() {
return this.bookInfo.every((item) => item.checked);
},
set(val) {
// console.log(val, 999);
this.bookInfo.map((item) => {
item.checked = val;
return item;
});
},
},
// 总价格
totalPrice() {
return this.$store.getters.totalPrice;
},
},
created() {
// 跳转路由时,隐藏下面tabbar
// this.$store.state.showMenu = false;
this.$store.commit("displayTabbar", false);
console.log( this.$store.state)
},
destroyed() {
// 跳转路由时,显示下面tabbar
// this.$store.state.showMenu = true;
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后,发送请求,得到数据后,渲染数据到页面
// 当路由跳转到detail页面时,接收传递过来的参数 就是id,并发送请求渲染数据到页面
created() {
// console.log(this.$route);
const pid = this.$route.params.id;
// console.log(pid);
// this.id = pid;
this.getData(pid);
this.recData();
},
methods:{
// 获取当前id的数据
async getData(id) {
const {
data: {
data: { result: bookInfo },
},
} = await this.$request.get("/books/" + id);
// console.log(bookInfo);
this.bookInfo = bookInfo[0];
// console.log(this.bookInfo)
},
// 获取推荐数据
async recData() {
const {
data: { data: recList },
} = await this.$request.get("/books", {
params: {
size: 6,
},
});
this.recommond = recList.result;
},
}
2、路由跳转到detail页面时,推荐数据的获取,通过发送请求后,渲染数据到页面
// 当路由跳转到detail页面时,接收传递过来的参数 就是id,并发送请求渲染数据到页面
created() {
// console.log(this.$route);
const pid = this.$route.params.id;
// console.log(pid);
// this.id = pid;
this.getData(pid);
this.recData();
},
methods:{
// 获取当前id的数据
async getData(id) {
const {
data: {
data: { result: bookInfo },
},
} = await this.$request.get("/books/" + id);
// console.log(bookInfo);
this.bookInfo = bookInfo[0];
// console.log(this.bookInfo)
},
// 获取推荐数据
async recData() {
const {
data: { data: recList },
} = await this.$request.get("/books", {
params: {
size: 6,
},
});
this.recommond = recList.result;
},
}
3、点击推荐商品后,把数据重新在detail页面上渲染(路由守卫)
// 路由内守卫
beforeRouteUpdate(to, from, next) {
// console.log(to, from);
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,
// name:123,
// age:25
},
});
}
4、添加商品到购物车,点击加入图书,先判断当前商品是否已经存在购物车中,如果存在,就让数量加1,否则不存在,添加到购物车中
// 添加书籍
addBook() {
// 添加当前商品到购物车;
// 判断当前商品是否已经存在购物车中
// 存在:数量+1
// 不存在:添加到购物车
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,
};
// 调用mutation方法
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) {
// console.log(this.$route)
this.$router.push({
name: "Cart",
params: {
id,
},
});
},
}
7、跳转路由时,隐藏和显示tabbar(因为下面的显示不一样)
mounted() {
// 跳转路由时,隐藏下面tabbar
// this.$store.state.showMenu = false;
this.$store.commit('displayTabbar',false)
},
destroyed() {
// 跳转路由时,显示下面tabbar
// this.$store.state.showMenu = true;
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,
// id: "",
bookInfo: {},
recommond: {},
};
},
methods: {
// 获取当前id的数据
async getData(id) {
const {
data: {
data: { result: bookInfo },
},
} = await this.$request.get("/books/" + id);
// console.log(bookInfo);
this.bookInfo = bookInfo[0];
// console.log(this.bookInfo)
},
// 获取推荐数据
async recData() {
const {
data: { data: recList },
} = await this.$request.get("/books", {
params: {
size: 6,
},
});
this.recommond = recList.result;
},
// 跳转到购物车
gotoCart(id) {
// console.log(this.$route)
this.$router.push({
name: "Cart",
params: {
id,
},
});
},
// 添加书籍
addBook() {
// 添加当前商品到购物车;
// 判断当前商品是否已经存在购物车中
// 存在:数量+1
// 不存在:添加到购物车
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,
};
// 调用mutation方法
this.$store.commit("add", goods);
}
},
// 跳转详情页
gotoDetail(id) {
this.$router.push({
name: "Detail",
params: {
id,
// name:123,
// age:25
},
});
},
// 图片显示大图
ImgPrev() {
console.log(1);
// ImagePreview([ this.bookInfo.imgurl,]);
ImagePreview({
images: [this.bookInfo.imgurl],
closeable: true,
});
},
// 导航栏 返回首页
onClickLeft() {
// Toast("返回首页");
this.$router.push("/home");
},
// 立即购买按钮 添加商品并跳转
buyNow() {
// 添加当前商品到购物车,并跳转到购物车页面
this.addBook();
this.$router.push("/cart");
},
},
computed: {
// 购物车的数据
cartlist() {
return this.$store.state.cart.bookInfo;
},
},
// 当路由跳转到detail页面时,接收传递过来的参数 就是id,并发送请求渲染数据到页面
created() {
// console.log(this.$route);
const pid = this.$route.params.id;
// console.log(pid);
// this.id = pid;
this.getData(pid);
this.recData();
},
mounted() {
// 跳转路由时,隐藏下面tabbar
// this.$store.state.showMenu = false;
this.$store.commit('displayTabbar',false)
},
destroyed() {
// 跳转路由时,显示下面tabbar
// this.$store.state.showMenu = true;
this.$store.commit('displayTabbar',true)
},
// 路由内守卫
beforeRouteUpdate(to, from, next) {
// console.log(to, from);
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
},
// 是否显示和隐藏 tabbar
showTabbar(){
return this.$store.state.common.showTabbar
}
},
created(){
// console.log(this.$store)
}
};
</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,
},
});
// console.log(books)
this.books = books.result;
this.page = this.books.length;
// console.log(this.books)
},
// 获取展示数据
async showData() {
const {
data: { data: showList },
} = await this.$request.get("/books", {
params: {
size: 10,
},
});
// console.log(books)
this.showList = showList.result;
// console.log(this.showList);
},
// 跳转详情页
gotoDetail(id) {
// this.$router.push(`/detail"${id}`)
this.$router.push({
name: "Detail",
params: {
id,
// name:123,
// age:25
},
});
},
},
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:'/home',
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({
// 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)
},
// 修改数据数量 传递参数 商品_id 数量qty
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
效果图
首页页面
购物车页面
详情页页面