这里先分析一下vue项目的文件结构,组件、入口文件及配置可以添加的位置。
项目结构
其中:
assets表示静态资源,如css,js
components是公共组件存放的位置
App.vue是根组件
main.js为入口文件

组件化

需要在项目src文件夹下components文件夹内创建Cart.vue文件作为购物车组件。
所以在新建完成后需要在App.vue根组件内使用。

组件按需引入及注册

import Cart from './components/Cart.vue'
export default{
	name: 'App',
	components: {
		cart:Cart
	}
}

// 组件使用
<cart></cart>   <!-- 在App.vue组件内id为App的div内 -->

这个过程就是,导入Cart.vue组件,然后对组件进行注册(components内的Cart注册),然后在#app内使用。

Cart组件的功能

实现了数据在cart内的添加,删减,数据的统计等。
在添加之前先实现商品展示界面(只写逻辑,没有样式):
书
今天突然发现我不太会写一步一步添加的代码步骤,能理解整个过程,但是不知道要一步一步怎么断开。
只能先放上整个代码过程,先一步一步解释吧,可能会比较乱,但是我会先放上整个文件内的全部代码。
首先:购物车数据:{
text:‘名字’,
price:‘单价’,
active: 是否选中,
count: 购买数量
}
这里有用到axios,需要先安装,使用下方命令,需要可以了解axios详细内容

npm i axios -S

全量代码

现在是先以App.vue作为父组件使用,全部代码如下:

App.vue文件

<template>
  <div id="app">
    <ul>
      <li v-for="(good, index) in goods" :key="good.id">
        <span>{{good.text}}</span>
        <span>¥{{good.price}}</span>
        <button @click="addGood(index)">加购物车</button>
      </li>
    </ul>

    <!-- 购物车 -->
    <cart :name="name"></cart>
  </div>
</template>

<script>
import Cart from './components/Cart.vue'
import axios from 'axios'

export default {
  data() {
    return {
      name: '测试购物车',
      text: '',
      goods:[]
    }
  },
  components: {
    cart:Cart
  },
  async created(){
    // 查询产品列表 使用axios
    try{
      const response = await axios.get('/api/goods');
      console.log(response);
      this.goods = response.data.list;
    }catch(err){
      console.error("error",err);
    }
  },
  methods: {
    addGood(i) {
      const good = this.goods[i];  // 获取goods中对应项  good为正在添加的商品
      this.$bus.$emit('addCart', good);  //第一个参数为事件名称,第二个参为需要派发的数据
    }
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Cart.vue

在components文件夹内:
文件

<template>
    <div>
        <p>{{name}}</p>
        <table border="1">
            <tr>
                <th>#</th>
                <th>课程名</th>
                <th>单价</th>
                <th>数量</th>
                <th>价格</th>
            </tr>
            <tr v-for="(c, i) in cart" :key="c.id" :class="{active:c.active}">
                <td>
                    <input type="checkbox" v-model="c.active"/>
                </td>
                <td>{{c.text}}</td>
                <td>¥{{c.price}}</td>
                <td>
                    <button @click="minus(i)">-</button>
                        {{c.count}}
                    <button @click="add(i)">+</button>
                </td>
                <td>{{c.price * c.count}}</td>
            </tr>

            <!-- 使用计算属性 -->
            <tr>
                <td></td>
                <td colspan="2">{{activeCount}}/{{count}}</td>
                <td colspan="2">¥{{total}}</td>
            </tr>
        </table>
    </div>
</template>

<script>
    export default {
        props: ['name'],     //传递给子元素的数据
        data() {
            return {
                // cart: []
                cart: JSON.parse(localStorage.getItem("cart")) || []
            }
        },
        // 数据持久化添加
        watch:{
            cart: {
                handler(n, o){
                    localStorage.setItem("cart", JSON.stringify(n));
                    console.log(o);
                },
                deep: true
            }
        },

        created(){
            this.cart = JSON.parse(window.localStorage.getItem('cart'));
            // 组件创建完成后执行一次,仅执行一次
            // 监听一下父组件添加商品的事件
            this.$bus.$on('addCart',good => {
                const ret = this.cart.find(v => v.id == good.id);  // 判断购物车内是否有该good,若有直接加数量,若无,直接加good
                if(ret){   // 购物车内已有该商品
                    ret.count += 1
                }else{
                  this.cart.push({     
                    ...good,     //rest参数
                    count:1,
                    active: true     //添加的东西是否是勾选状态
                  })
                }
            })
        },
        methods: {
            // 数据持久化
            setLocal(){
                window.localStorage.setItem('cart',JSON.stringify(this.cart));
            },

            minus(i) {
                const count = this.cart[i].count;
                if(count > 1){
                    this.cart[i].count -= 1;
                }else{
                    this.remove(i);
                }
            },
            add(i){
                this.cart[i].count += 1;
            },
            remove(i){
                if(window.confirm("确定删除?")){
                    this.cart.splice(i,1);
                }
            }
        },
        computed: {
            activeCount() {    // 过滤处激活项的数量 
                return this.cart.filter(v => v.active).length;
            },
            count(){
                return this.cart.length;
            },
            total(){   //计算激活项总价
                let num = 0;
                this.cart.forEach(c => {
                    if(c.active){
                        num += c.price * c.count;
                    }
                });
                return num;
            }
        },
    }
</script>

<style scoped>
.active{
    color: lightgreen;
}
</style>

大体两个文件完了,还需要修改其他文件。

main.js

main.js文件内,需要添加:

Vue.prototype.$bus=  new Vue();

这个是修改了Vue实例,创建了只用于派发消息的实例。
在代码内用到了axios,则需要我们来mock数据,新建vue.config.js文件,与package.json同级。

vue.config.js

// 用于mock数据
module.exports = {
    configureWebpack: {
        devServer: {
            before(app){
                app.get('/api/goods', function(req,res){
                	//当请求成功时
                    res.json({
                        code: 0,  //成功
                        list: [
                            {id:1,text:'JS高程',price: 100},
                            {id:2,text:'Java基础',price: 100}
                        ]
                    })
                })
            }
        }
    }
}

创建这个购物车,所需要的修改及模拟数据部分就全部涉及这四个文件。

操作过程解析

因为先po了全部的代码,现在需要分析几个要点:
这实现了购物车的添加、删除、选中、金额统计、模拟数据获取等操作。

模拟数据(vue.config.js文件)

采用了vue cli官网内使用webpack的做法,模拟数据,配置文件参考vue cli内的Workding with webpack。这里就用到了之前安装的axios插件。简单的mock,使用自带的webpack-dev-server即可,新建vue.config.js扩展webpack配置。
直接访问http://localhost:8080/api/goods就可以看到mock数据。
启动mock模拟数据准备好了,使用axios获取数据,在App.vue内可以看到在created内获取初始数据。

//请求数据  这个是直接使用同步方式写异步代码
    axios.get("/api/goods").then(response => {
       this.goods = response.data.list;
    }).catch(err => {
      // 错误处理
    })

但是采用async await异步方式来进行访问,下方的原代码:

async created(){
    // 查询产品列表 使用axios
    try{
      const response = await axios.get('/api/goods');
      console.log(response);
      this.goods = response.data.list;
    }catch(err){
      console.error("error",err);
    }
  },

现在这个里面采用了async /await异步处理方式来通过axios来查询数据,同时还采用了try…catch来处理可能抛出的异常信息。
具体可以了解的async/awaitaxios
现在可以看到最后的结果是this.goods,所以在使用时,全量的数据在goods内。所以在App.vue内的li标签内的数据使用goods。

循环

这里在li中使用了循环,用于将goods数据内的每一项都渲染到页面内。

<ul>
      <li v-for="(good, index) in goods" :key="good.id">
        <span>{{good.text}}</span>
        <span>¥{{good.price}}</span>
        <button @click="addGood(index)">加购物车</button>
      </li>
    </ul>

这里采用了v-for指令进行循环,提供了两个参数(good, index)为键名,现在使用循环必须使用key(不明可看此文)。
在循环渲染完成后,可以看到:
v-for渲染

组件传值

1.props

在App.vue内使用组件<cart :name="name"></cart>, 可以看到组件App.vue给子组件cart传递了属性name,这个name获取的是data内的值:

export default {
  data() {
    return {
      name: '测试购物车',
      text: '',
      goods:[]
    }
  }
 }

父组件传递这个值之后,子组件通过props获取到这个值:

props: ['name'],

然后在html内渲染:

<p>{{name}}</p>

最后渲染可以看到:
props

2.总线(bus)传值

总线传值可用于非父子组件间的传值,在代码内有使用总线传值。
需要先在Vue上创建总线的实例,等于为创建了一个新的vue,用于派发消息:

Vue.prototype.$bus=  new Vue();

在添加时间的按钮上添加点击事件addGood(index)事件,

methods: {
    addGood(i) {
      const good = this.goods[i];  // 获取goods中对应项  good为正在添加的商品
      this.$bus.$emit('addCart', good);  //第一个参数为事件名称,第二个参为需要派发的数据
    }
  },

上面的代码:good为现在正在点击 加购物车 的那一项东西,获取该项的索引,然后使用this.$bus.$emit对添加购物车addGood的操作,将每个加购物车的数据good派发出去。
这个等于是父组件通知,我组件内的某一个东西加购物车了,加的是这个东西,然后将这些全部发给组件,由子组件进行购物车动作添加的步骤。

子组件Cart.vue通过this.$bus.$on来接受这个动作,得到了需要添加的数据,然后将这些数据渲染或存储等。
子组件内对此过程的全部操作:

created(){
            // 组件创建完成后执行一次,仅执行一次
            // 监听一下父组件添加商品的事件
            this.$bus.$on('addCart',good => {
                const ret = this.cart.find(v => v.id == good.id);  // 判断购物车内是否有该good,若有直接加数量,若无,直接加good
                if(ret){   // 购物车内已有该商品
                    ret.count += 1
                }else{
                  this.cart.push({     
                    ...good,     //rest参数
                    count:1,
                    active: true     //添加的东西是否是勾选状态
                  })
                }
            })
        },

在这个渲染完成后,可能会:
bus传值结果

事件处理

同时在数量上有对数量的加减统计,minusadd

<tr v-for="(c, i) in cart" :key="c.id" :class="{active:c.active}">
                <td>
                    <input type="checkbox" v-model="c.active"/>
                </td>
                <td>{{c.text}}</td>
                <td>¥{{c.price}}</td>
                <td>
                    <button @click="minus(i)">-</button>
                        {{c.count}}
                    <button @click="add(i)">+</button>
                </td>
                <td>{{c.price * c.count}}</td>
            </tr>

通过方法实现:

methods: {
            minus(i) {
                const count = this.cart[i].count;
                if(count > 1){
                    this.cart[i].count -= 1;
                }else{
                    this.remove(i);
                }
            },
            add(i){
                this.cart[i].count += 1;
            },
            remove(i){
                if(window.confirm("确定删除?")){
                    this.cart.splice(i,1);
                }
            }
        },

动态样式

$bus内传值时,有给cart添加了active属性,同时设置了多选框,

<tr v-for="(c, i) in cart" :key="c.id" :class="{active:c.active}">
                <td>
                    <input type="checkbox" v-model="c.active"/>
                </td>
  	......
</tr>

在这里多选框内使用v-model双向绑定,控制上面的tr这一行是否添加classactive的属性,若选中则将这一行字体变为浅绿色。

统计数据(计算属性)

这里采用计算属性。
子组件html部分:

<tr>
    <td></td>
    <td colspan="2">{{activeCount}}/{{count}}</td>
    <td colspan="2">¥{{total}}</td>
</tr>

添加后,计算这几个需要存放的数量。activeCount是选中的数量,count是总数,total是总价。
在子组件内添加computed属性:

computed: {
    activeCount() {    // 过滤处激活项的数量 
        return this.cart.filter(v => v.active).length;
    },
    count(){
        return this.cart.length;
    },
    total(){   //计算激活项总价
        let num = 0;
        this.cart.forEach(c => {
            if(c.active){
                num += c.price * c.count;
            }
        });
        return num;
    }
},

数据持久化

每次在刷新网页时,内部添加的购物车数据会被全部清除。所以需要将需要的购物车数据保存在localStorage中,在每次刷新购物车之后然后获取localStorage内存储的数据,进行展示处理。
对数据缓存的一个获取:

data() {
	return {
		cart: JSON.parse(localStorage.getItem("cart")) || []
	}
},

监听数据,在数据发生变化时,修改内部的缓存:

watch:{
	cart: {
		handler(n, o){
			localStorage.setItem("cart", JSON.stringify(n));
			console.log(o);
		},
		deep: true
	}
},

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

// JSON.stringify()的作用是将 JavaScript 对象转换为 JSON 字符串
window.localStorage.setItem('cart',JSON.stringify(this.cart));
//JSON.parse()将JSON字符串转为一个对象。
this.cart = JSON.parse(window.localStorage.getItem('cart'));

大体这个Vue实现简单购物车涉及到的完了,比较简单,但是涉及到的东西也不少,axios、async/await、事件处理、动态样式、mock数据、组件传值、数据持久化等。

Logo

前往低代码交流专区

更多推荐