Vuex案例-购物车

Study Notes

案例-购物车

购物车 Demo,带你理解并使用 vuex 状态管理

目录结构

在这里插入图片描述

效果展示

在这里插入图片描述

功能介绍

  • 首页
    • 获取并展示商品列表
    • 添加商品到购物车
    • 展示购物车中商品总数
  • 购物车
    • 更新购物车中的商品数据
    • 更新商品选中状态
    • 计算选中商品数量、合计商品价格
    • 全选、单选
    • 删除购物车中的商品
    • 本地缓存 vuex 状态

接单

小编承接外包,有意者可加
QQ:1944300940
在这里插入图片描述

微信号:wxid_g8o2y9ninzpp12
在这里插入图片描述

功能开发

Vuex 中创建两个模块,分别用来记录商品列表和购物车的状态,store 的结构:

└───store
    ├───modules
    │   └───cart.js
    │   └───production.js
    └───index.js

视图结构

└───view
    ├───cart
    │   └───index.vue
    └───home
        └───index.vue

首页

production.js

/**
 * @author Wuner
 * @date 2020/8/20 16:39
 * @description
 */
const state = {
  // 商品数组
  productions: [],
};
const getters = {};
const mutations = {
  /**
   * 设置商品数组
   * @param state
   * @param payload
   */
  setProductions(state, payload) {
    state.productions = payload.map((val) => {
      // 价格保留两位小数
      val.price = val.price.toFixed(2);
      return val;
    });
  },
};
const actions = {
  /**
   * 调用接口获取商品
   * @param commit
   * @returns {Promise<void>}
   */
  async getProductions({ commit }) {
    const result = await this._vm.$http.get(
      `http://${process.env.HOST}:3000/products`,
      {},
    );
    commit('setProductions', result);
  },
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

cart.js

/**
 * @author Wuner
 * @date 2020/8/20 16:39
 * @description
 */

const state = {
  // 购物车商品数组
  productionsCart: [],
};
const getters = {
  /**
   * 商品总数
   * @param state
   * @returns {number}
   */
  totalCount(state) {
    return state.productionsCart.reduce((sum, { count }) => sum + count, 0);
  },
};
const mutations = {
  /**
   * 将商品添加到购物车
   * @param state
   * @param payload
   */
  addToCarts(state, payload) {
    let production = state.productionsCart.find(
      (value) => value.id === payload.id,
    );
    // 1. productionsCart 中没有该商品,把该商品添加到数组,并增加 count,isChecked,totalPrice
    // 2. cartProducts 有该商品,让商品的数量加payload.count,选中,计算小计
    // 3. 计算小计时,保留两位小数
    if (production) {
      production.count = production.count += payload.count;
      production.isChecked = true;
      production.totalPrice = (production.price * production.count).toFixed(2);
    } else {
      state.productionsCart.push({
        ...payload,
        isChecked: true,
        totalPrice: (payload.price * payload.count).toFixed(2),
      });
    }
  },
};
const actions = {};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

home/index.vue

<template>
  <div class="home">
    <van-nav-bar title="首页" fixed placeholder>
      <template slot="right">
        <van-icon
          size="24"
          name="cart-o"
          @click="$router.push('cart')"
          :badge="totalCount || ''"
        />
      </template>
    </van-nav-bar>
    <div v-for="(item, index) in productions" :key="item.id" class="home-card">
      <van-card
        :num="nums[index]"
        :price="item.price"
        :desc="item.desc"
        :title="item.title"
        :thumb="item.thumb"
      >
        <template slot="footer">
          <div class="home-card-footer">
            <van-stepper v-model="nums[index]" />
            <van-button @click="addCart(item, index)" icon="cart-o" size="mini">
              加入购物车
            </van-button>
          </div>
        </template>
      </van-card>
    </div>
  </div>
</template>

<script>
import { mapActions, mapGetters, mapMutations, mapState } from 'vuex';

export default {
  data() {
    return {
      nums: [],
    };
  },
  methods: {
    // 使用 action 辅助函数
    // 映射 this.getProductions() 为 this.$store.dispatch('production/getProductions')
    ...mapActions('production', ['getProductions']),
    // 使用 mutation 辅助函数
    // 映射 this.addToCarts() 为 this.$store.commit('cart/addToCarts')
    ...mapMutations('cart', ['addToCarts']),
    // 添加商品到购物车
    addCart(item, index) {
      let count = this.nums[index];
      // 将商品数量置为1
      this.nums.splice(index, 1, 1);
      // 以载荷方式提交
      this.addToCarts({ ...item, count });
    },
    // 初始化商品数据
    initData() {
      // 获取商品列表数据
      this.getProductions();
      // 初始化商品步进器数值
      this.productions.forEach(() => this.nums.push(1));
    },
  },
  created() {
    this.initData();
  },
  mounted() {},
  computed: {
    // 使用 state 辅助函数
    // 映射 this.productions 为 this.$store.state.production.productions
    ...mapState('production', ['productions']),
    // 使用 getter 辅助函数
    // 映射 this.totalCount 为 this.$store.getters['cart/totalCount']
    ...mapGetters('cart', ['totalCount']),
  },
};
</script>
<style scoped lang="less">
.home {
  position: absolute;
  width: 100%;
  min-height: 100%;
  background-color: #f5f5f5;

  &-card {
    margin: 10px 16px 0 16px;

    .van-card {
      border-radius: 8px;
    }

    &-footer {
      margin-top: 10px;
      display: flex;
      align-items: center;
      justify-content: flex-end;
    }
  }
}
</style>

购物车

cart.js

/**
 * @author Wuner
 * @date 2020/8/20 16:39
 * @description
 */
import Local from '../../utils/local';

const state = {
  // 购物车商品数组
  productionsCart: Local.get('productionsCart') || [],
};
const getters = {
  /**
   * 是否选中所有商品
   * @param state
   * @returns {boolean}
   */
  isAllChecked(state) {
    let result = true;
    for (let production of state.productionsCart) {
      // 当购物车中有一个商品未选中,就返回false
      if (!production.isChecked) {
        result = false;
        break;
      }
    }
    return result;
  },
  /**
   * 商品总数
   * @param state
   * @returns {number}
   */
  totalCount(state) {
    return state.productionsCart.reduce((sum, { count }) => sum + count, 0);
  },
  /**
   * 选中商品的总数
   * @param state
   * @returns {number}
   */
  checkedCount(state) {
    return state.productionsCart.reduce(
      (sum, { count, isChecked }) => (isChecked ? sum + count : sum),
      0,
    );
  },
  /**
   * 选中商品的价格合计
   * @param state
   * @returns {number}
   */
  checkedPrice(state) {
    return state.productionsCart.reduce(
      (sum, { totalPrice, isChecked }) =>
        isChecked ? sum + totalPrice * 100 : sum,
      0,
    );
  },
};
const mutations = {
  /**
   * 更新商品的选中状态
   * @param state
   * @param payload
   */
  updateProductionChecked(state, payload) {
    // 全选
    if (payload.all) {
      state.productionsCart.forEach(
        (production) => (production.isChecked = payload.isChecked),
      );
    } else {
      // 单选
      let production = state.productionsCart.find(
        (value) => value.id === payload.id,
      );
      production && (production.isChecked = !payload.isChecked);
    }
  },
  /**
   * 更新购物车中的商品数据
   * @param state
   * @param payload
   */
  updateProduction(state, payload) {
    let production = state.productionsCart.find(
      (value) => value.id === payload.id,
    );
    production.count = payload.count;
    production.totalPrice = (production.count * production.price).toFixed(2);
  },
  /**
   * 将商品添加到购物车
   * @param state
   * @param payload
   */
  addToCarts(state, payload) {
    let production = state.productionsCart.find(
      (value) => value.id === payload.id,
    );
    // 1. productionsCart 中没有该商品,把该商品添加到数组,并增加 count,isChecked,totalPrice
    // 2. cartProducts 有该商品,让商品的数量加payload.count,选中,计算小计
    // 3. 计算小计时,保留两位小数
    if (production) {
      production.count = production.count += payload.count;
      production.isChecked = true;
      production.totalPrice = (production.price * production.count).toFixed(2);
    } else {
      state.productionsCart.push({
        ...payload,
        isChecked: true,
        totalPrice: (payload.price * payload.count).toFixed(2),
      });
    }
  },
  /**
   * 移除购物车中的商品
   * @param state
   * @param payload
   */
  removeFromCarts(state, payload) {
    let index = state.productionsCart.findIndex(
      (value) => value.id === payload,
    );
    index >= 0 && state.productionsCart.splice(index, 1);
  },
};
const actions = {};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

cart/index.vue

<template>
  <div class="cart">
    <van-nav-bar
      left-arrow
      @click-left="$router.back()"
      title="购物车"
      fixed
      placeholder
    />
    <div v-for="item in productionsCart" :key="item.id" class="cart-card">
      <van-swipe-cell>
        <van-card
          :num="item.num"
          :price="item.price"
          :desc="item.desc"
          :title="item.title"
          :thumb="item.thumb"
        >
          <template slot="footer">
            <div class="cart-card-footer">
              <div class="cart-card-footer-left">
                <van-checkbox
                  @click="updateProductionChecked(item)"
                  :value="item.isChecked"
                  :name="item.id"
                />
                <span class="total-price">小计: ¥{{ item.totalPrice }}</span>
              </div>
              <van-stepper
                @change="onChange($event, item.id)"
                :default-value="item.count"
              />
            </div>
          </template>
        </van-card>
        <template slot="right">
          <van-button
            @click="removeFromCarts(item.id)"
            square
            text="删除"
            type="danger"
            class="delete-button"
          />
        </template>
      </van-swipe-cell>
    </div>
    <van-submit-bar
      :price="checkedPrice"
      button-text="提交订单"
      @submit="onSubmit"
    >
      <van-checkbox
        @click="
          updateProductionChecked({ all: true, isChecked: !isAllChecked })
        "
        :value="isAllChecked"
      >
        全选
      </van-checkbox>
      <template slot="tip">
        <span
          >已选 <span>{{ checkedCount }}</span> 件商品</span
        >
      </template>
    </van-submit-bar>
  </div>
</template>

<script>
import { mapGetters, mapMutations, mapState } from 'vuex';

export default {
  data() {
    return {};
  },
  methods: {
    // 使用 mutation 辅助函数
    // 映射 this.updateProduction() 为 this.$store.commit('cart/updateProduction')
    // 映射 this.updateProductionChecked() 为 this.$store.commit('cart/updateProductionChecked')
    // 映射 this.removeFromCarts() 为 this.$store.commit('cart/removeFromCarts')
    ...mapMutations('cart', [
      'updateProduction',
      'updateProductionChecked',
      'removeFromCarts',
    ]),
    onSubmit() {},
    // 监听步进器数据改变,更新购物车中的商品数据
    onChange(count, id) {
      typeof count === 'number' && this.updateProduction({ count, id });
    },
  },
  created() {},
  mounted() {},
  computed: {
    // 使用 state 辅助函数
    // 映射 this.productionsCart 为 this.$store.state.cart.productionsCart
    ...mapState('cart', ['productionsCart']),
    // 使用 getter 辅助函数
    // 映射 this.isAllChecked 为 this.$store.getters['cart/isAllChecked']
    // 映射 this.checkedCount 为 this.$store.getters['cart/checkedCount']
    // 映射 this.checkedPrice 为 this.$store.getters['cart/checkedPrice']
    ...mapGetters('cart', ['isAllChecked', 'checkedCount', 'checkedPrice']),
  },
};
</script>
<style scoped lang="less">
.cart {
  position: absolute;
  width: 100%;
  min-height: 100%;
  background-color: #f5f5f5;

  &-card {
    margin: 10px 16px 0 16px;

    .van-card {
      border-radius: 8px;
    }

    &-footer {
      margin-top: 10px;
      display: flex;
      align-items: center;
      justify-content: space-between;

      &-left {
        display: flex;
        align-items: center;

        .total-price {
          margin-left: 16px;
          color: #ee0a24;
          font-size: 14px;
        }
      }
    }

    .delete-button {
      height: 100%;
    }
  }
}
</style>

本地缓存购物车 vuex 状态

store/index.js

/**
 * @author Wuner
 * @date 2020/8/20 16:38
 * @description
 */
import Vuex from 'vuex';
import Vue from 'vue';
import production from './modules/production';
import cart from './modules/cart';

Vue.use(Vuex);
import Local from '../utils/local';

const myPlugin = (store) => {
  // 当 store 初始化后调用
  store.subscribe((mutation, state) => {
    // 每次 mutation 之后调用
    // mutation 的格式为 { type, payload }
    // 缓存cart模块状态
    mutation.type.startsWith('cart/') &&
      Local.set('productionsCart', state.cart.productionsCart);
  });
};

const state = {};
const getters = {};
const mutations = {};
const actions = {};

const store = new Vuex.Store({
  // 非生产环境添加严格模式
  strict: process.env.NODE_ENV !== 'production',
  state,
  getters,
  mutations,
  actions,
  // 模块
  modules: {
    production,
    cart,
  },
  // 插件
  plugins: [myPlugin],
});

export default store;

demo 源码地址

下一篇——Vuex 模拟实现

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白驹过隙时光荏苒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值