Vue

使用Vuex实现购物车功能

2023-08-20  本文已影响0人  h2coder

效果展示

image.png

依赖版本

准备数据

购物车列表

{
  "cart": [
    {
      "id": 100001,
      "name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
      "price": 128,
      "count": 2,
      "thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
    },
    {
      "id": 100002,
      "name": "网易味央黑猪猪肘330g*1袋",
      "price": 39,
      "count": 2,
      "thumb": "https://yanxuan-item.nosdn.127.net/d0a56474a8443cf6abd5afc539aa2476.jpg"
    },
    {
      "id": 100003,
      "name": "KENROLL男女简洁多彩一片式室外拖",
      "price": 128,
      "count": 2,
      "thumb": "https://yanxuan-item.nosdn.127.net/eb1556fcc59e2fd98d9b0bc201dd4409.jpg"
    },
    {
      "id": 100004,
      "name": "云音乐定制IN系列intar民谣木吉他",
      "price": 589,
      "count": 1,
      "thumb": "https://yanxuan-item.nosdn.127.net/4d825431a3587edb63cb165166f8fc76.jpg"
    }
  ]
}

页面分析

业务分析

Vuex配置

开始配置

/* 封装Vuex相关 */
import Vue from 'vue'
import Vuex from 'vuex'

// 导入购物车模块的store
import cart from '@/store/modules/cart'

// 使用Vuex
Vue.use(Vuex)

// 创建vuex仓库实例
const vuex = new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    // 导入购物车模块的store
    cart
  }
})

// 默认导出
export default vuex

注册Vuex

import Vue from 'vue'
import App from './App.vue'
// 导入vuex实例
import store from './store'

Vue.config.productionTip = false

new Vue({
  // 注册vuex
  store,
  render: h => h(App)
}).$mount('#app')

购物车模块Vuex

/* 购物车模块的store */

// 引入axios
import axios from "axios"

// 状态响应式变量
const state = () => ({
    // 购物车数据列表
    list: []
})

// vuex的计算属性
const getters = {
    // 总价格
    totalPrice(state) {
        return state.list.reduce((prev, current) => {
            return prev + (current.count * current.price)
        }, 0)
    },
    // 总数量
    totalCount(state) {
        return state.list.reduce((prev, current) => {
            return prev + current.count
        }, 0)
    }
}

// 同步修改state变量
const mutations = {
    // 更新购物车数据
    updateCartList(state, list) {
        state.list = list
    },
    // 更新指定Id的购物车商品的数量
    updateCartCount(state, { id, count }) {
        const cartItem = state.list.find(item => item.id === id)
        cartItem.count = count
    }
}

// 异步修改state变量
const actions = {
    // 发送请求,获取购物车列表 
    async getCartAsync(context) {
        const result = await axios({
            url: 'http://localhost:3000/cart',
            method: 'GET'
        })
        console.log(result);

        // 提交mutation
        const list = result.data
        context.commit("updateCartList", list)
    },
    // 修改购物车的商品数量
    async updateAsyncCartCount(context, { id, count }) {
        // 发送请求,修改数量
        const result = await axios({
            url: `http://localhost:3000/cart/${id}`,
            method: 'PATCH',
            data: {
                count: count
            }
        })
        console.log(result);

        // 提交mutation,修改内存中购物车商品的数量
        context.commit("updateCartCount", { id, count })
    }
}

// 默认导出
export default {
    // 命名空间,设置为true,才能用使用vuex的辅助函数
    namespaced: true,
    state,
    getters,
    mutations,
    actions
}

编写组件

按页面分析,拆分为3个组件,分别为顶部栏组件、列表项组件、底部栏组件

顶部栏组件

<template>
  <div class="header-container">{{ title }}</div>
</template>

<script>
export default {
  name: "CartHeader",
  props: {
    // 标题文字
    title: {
      type: String,
      required: true
    }
  }
};
</script>

<style lang="less" scoped>
.header-container {
  height: 50px;
  line-height: 50px;
  font-size: 16px;
  background-color: #42b983;
  text-align: center;
  color: white;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 999;
}
</style>

底部栏组件

<template>
  <div class="footer-container">
    <!-- 中间的合计 -->
    <div>
      <span>共 {{ totalCount }} 件商品,合计:</span>
      <span class="price">¥{{ totalPrice }}</span>
    </div>
    <!-- 右侧结算按钮 -->
    <button class="btn btn-success btn-settle">结算</button>
  </div>
</template>

<script>
export default {
  name: "CartFooter",
  props: {
    // 总价格
    totalPrice: {
      type: Number,
      required: true,
    },
    // 总数量
    totalCount: {
      type: Number,
      required: true,
    },
  },
};
</script>

<style lang="less" scoped>
.footer-container {
  background-color: white;
  height: 50px;
  border-top: 1px solid #f8f8f8;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 0 10px;
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 999;
}

.price {
  color: red;
  font-size: 13px;
  font-weight: bold;
  margin-right: 10px;
}

.btn-settle {
  height: 30px;
  min-width: 80px;
  margin-right: 20px;
  border-radius: 20px;
  background: #42b983;
  border: none;
  color: white;
}
</style>

购物车项组件

<template>
  <div class="goods-container">
    <!-- 左侧图片区域 -->
    <div class="left">
      <img :src="item.thumb" class="avatar" alt="" />
    </div>
    <!-- 右侧商品区域 -->
    <div class="right">
      <!-- 标题 -->
      <div class="title">{{ item.name }}</div>
      <div class="info">
        <!-- 单价 -->
        <span class="price">¥{{ item.price }}</span>
        <div class="btns">
          <!-- 按钮区域 -->
          <button
            :disabled="isDisable"
            @click="updateCartCount(item.id, -1)"
            class="btn btn-light"
          >
            -
          </button>
          <span class="count">{{ item.count }}</span>
          <button @click="updateCartCount(item.id, 1)" class="btn btn-light">
            +
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
// 引入vuex的辅助函数
import { mapActions } from "vuex";

export default {
  name: "CartItem",
  // 子组件参数声明
  props: {
    item: {
      type: Object,
      required: true,
    },
  },
  methods: {
    // 更新购物车的商品数量
    updateCartCount(id, unit) {
      // 计算新商品数量
      const count = this.item.count + unit;
      // 派发action,修改数量
      this.updateAsyncCartCount({
        id: id,
        count: count,
      });
    },
    // 引入异步action
    ...mapActions("cart", ["updateAsyncCartCount"]),
  },
  computed: {
    // 当购物车的商品数量小于等于1时,为禁用
    isDisable() {
      return this.item.count <= 1;
    },
  },
};
</script>

<style lang="less" scoped>
.goods-container {
  display: flex;
  padding: 10px;
  + .goods-container {
    border-top: 1px solid #f8f8f8;
  }
  .left {
    .avatar {
      width: 100px;
      height: 100px;
    }
    margin-right: 10px;
  }
  .right {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;
    .title {
      font-weight: bold;
    }
    .info {
      display: flex;
      justify-content: space-between;
      align-items: center;
      .price {
        color: red;
        font-weight: bold;
      }
      .btns {
        .count {
          display: inline-block;
          width: 30px;
          text-align: center;
        }
      }
    }
  }
}

.custom-control-label::before,
.custom-control-label::after {
  top: 3.6rem;
}
</style>

根组件

<template>
  <div class="app-container">
    <!-- Header 区域 -->
    <cart-header title="购物车"></cart-header>
    <!-- 商品 Item 项组件 -->
    <cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>
    <!-- Foote 区域 -->
    <cart-footer
      :totalPrice="totalPrice"
      :totalCount="totalCount"
    ></cart-footer>
  </div>
</template>

<script>
// 标题栏
import CartHeader from "@/components/cart-header.vue";
// 购物车条目
import CartItem from "@/components/cart-item.vue";
// 底部栏
import CartFooter from "@/components/cart-footer.vue";

// 引入vuex的辅助函数
import { mapState, mapActions, mapGetters } from "vuex";

export default {
  name: "App",
  // 注册组件
  components: {
    CartHeader,
    CartFooter,
    CartItem,
  },
  created() {
    // 一进入页面,就派发action,获取购物车列表数据
    this.getCartAsync();
  },
  methods: {
    // 引入异步action
    ...mapActions("cart", ["getCartAsync"]),
  },
  computed: {
    // 引入状态响应式变量
    ...mapState("cart", ["list"]),
    // 引入vuex的计算属性
    ...mapGetters("cart", ["totalPrice", "totalCount"]),
  },
};
</script>

<style lang="less" scoped>
.app-container {
  padding: 50px 0;
  font-size: 14px;
}
</style>
上一篇 下一篇

猜你喜欢

热点阅读