【Vue2】综合案例 - 购物车

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


1.功能模块分析

  1. 请求动态渲染购物车,数据存Vuex
  2. 数字框控件修改数据
  3. 动态计算总价和总数量

2.脚手架新建项目(注意:勾选vuex)

vue create vue-cart-demo

3.功能实现

  1. 新建store/modules/cart.js

(1) 语法一:

const state = {}
const getters = {}
const mustations = {}
const actions = {}
export default {
  namespaced: true,
  state,
  mustations,
  getters,
  actions
}

(2) 语法二(更推荐):

// cart 购物车模块
export default {
  namespaced: true,
  state () {
    return {
      // 购物车数据 [{},{}]
      list: []
    }
  },
  getters: {},
  mutations: {},
  actions: {}
}
  1. 基于json-server 工具,准备后端接口服务环境
    当后端接口没有准备就绪时,可以利用一些工具,例如json-server,快速帮我们生成一套增删改查的接口

(1)安装全局工具 json-server (全局工具仅需要安装一次)

yarn global add json-server 或  npm i json-server  -g

(2)代码根目录新建一个 db 目录
在这里插入图片描述

(3)新建一个json文件,此处不用新建,直接将资料 index.json 移入 db 目录
在这里插入图片描述

(4)进入 db 目录,执行命令,启动后端接口服务 (使用–watch 参数 可以实时监听 json 文件的修改)

json-server  --watch  index.json

此处报错
在这里插入图片描述
解决:

  1. 全局安装指令改为:
npm install -g json-server
  1. 查看Json-server版本
json-server --version
  1. 创建Json文件
{
  "cart": [
    {
      "id": 100001,
      "name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
      "price": 128,
      "count": 1,
      "thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
    },
    {
      "id": 100002,
      "name": "网易味央黑猪猪肘330g*1袋",
      "price": 39,
      "count": 10,
      "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"
    }
  ],
  "friends":[
  { "id":1,"name":"zs","age":18},
  { "id":2,"name":"ls","age":16},
  { "id":3,"name":"ww","age":20}
  ]
}

  1. 进入JSON文件所在文件夹, (使用–watch 参数 可以实时监听 json 文件的修改)
json-server  --watch  index.json

在这里插入图片描述
可以通过: http://localhost:3000/friends 访问

在这里插入图片描述
http://localhost:3000/friends/1 就是访问friends下id=1的数据
在这里插入图片描述
http://localhost:3000/cart/100001 就是id=100001的数据
在这里插入图片描述
(5)安装axios
(6)代码展示
cart.js 代码

// cart 购物车模块
import axios from 'axios'
export default {
  namespaced: true,
  state () {
    return {
      // 购物车数据 [{},{}]
      list: []
    }
  },
  getters: {
    goodsCount (state) {
      return state.list.reduce((sum, item) => sum + item.count, 0)
    },
    priceAll (state) {
      return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
    }
  },
  mutations: {
    update (state, newList) {
      state.list = newList
    },
    resetCount (state, newValue) {
      const restItem = state.list.find(item => item.id === newValue.id)
      restItem.count = newValue.count
    }
  },
  actions: {
    // 查
    async asyncGetData (context, newData) {
      const res = await axios.get('http://localhost:3000/cart')
      console.log(res.data)
      newData = res.data
      context.commit('update', newData)
    },
    // 改
    async asyncChangeData (context, obj) {
      await axios.patch('http://localhost:3000/cart/' + obj.id, { count: obj.count })
      // 2.更新仓库中的数据
      context.commit('resetCount', obj)
    }
  }
}

cart-item.vue 代码

<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 class="btn btn-light" @click="onBtnClick(-1)">-</button>
          <span class="count">{{item.count}}</span>
          <button class="btn btn-light" @click="onBtnClick(1)">+</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CartItem',
  props: {
    item: Object
  },
  methods: {
    onBtnClick (step) {
      const newCount = (this.item.count + step) || 1
      if (newCount < 1) return

      // 发送修改数量请求
      this.$store.dispatch('cart/updateCount', {
        id: this.item.id,
        count: newCount
      })
    }
  }
}
</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>

cart-header.vue 代码

<template>
  <div class="header-container">购物车案例</div>
</template>

<script>
export default {
  name: 'CartHeader'
}
</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>

cart-footer.vue 代码

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

<script>
import { mapGetters } from 'vuex'
export default {
  name: 'CartFooter',
  computed: {
    ...mapGetters('cart', ['total', 'totalPrice'])
  }
}
</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>

App.vue代码

<template>
  <div class="app-container">
    <!-- Header 区域 -->
    <cart-header></cart-header>

    <!-- 商品 Item 项组件 -->
    <cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>

    <!-- Foote 区域 -->
    <cart-footer></cart-footer>
  </div>
</template>

<script>
import CartHeader from '@/components/cart-header.vue'
import CartFooter from '@/components/cart-footer.vue'
import CartItem from '@/components/cart-item.vue'
import { mapState } from 'vuex'

export default {
  name: 'App',
  components: {
    CartHeader,
    CartFooter,
    CartItem
  },
  created () {
    this.$store.dispatch('cart/getList')
  },
  computed: {
    ...mapState('cart', ['list'])
  }
}
</script>

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

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值