vuex之webApp购物流程实现

tech2024-07-15  65

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:{ // 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

效果图

首页页面 购物车页面 详情页页面

最新回复(0)