Vue购物车案例

Vue购物车案例

需求分析

1.按照组件方式实现业务需求

  • 根据业务功能进行组件划分
    • 标题组件(展示文本)
    • 列表组件(列表展示、商品数量变更、商品删除)
    • 结算组件(计算商品总额)

实现步骤

1.功能实现步骤

  • 实现整体布局和样式效果
  • 划分独立的功能组件
  • 组合所有的子组件形成整体结构
  • 逐个实现各个组件功能
    • 标题组件
    • 列表组件
    • 结算组件

CSS 样式

<style type="text/css">
.container {
}
.container .cart {
    width: 300px;
    margin: auto;
}
.container .title {
    background-color: lightblue;
    height: 40px;
    line-height: 40px;
    text-align: center;
    /*color: #fff;*/  
}
.container .total {
    background-color: #FFCE46;
    height: 50px;
    line-height: 50px;
    text-align: right;
}
.container .total button {
    margin: 0 10px;
    background-color: #DC4C40;
    height: 35px;
    width: 80px;
    border: 0;
}
.container .total span {
    color: red;
    font-weight: bold;
}
.container .item {
    height: 55px;
    line-height: 55px;
    position: relative;
    border-top: 1px solid #ADD8E6;
}
.container .item img {
    width: 45px;
    height: 45px;
    margin: 5px;
}
.container .item .name {
    position: absolute;
    width: 90px;
    top: 0;left: 55px;
    font-size: 16px;
}

.container .item .change {
    width: 100px;
    position: absolute;
    top: 0;
    right: 50px;
}
.container .item .change a {
    font-size: 20px;
    width: 30px;
    text-decoration:none;
    background-color: lightgray;
    vertical-align: middle;
}
.container .item .change .num {
    width: 40px;
    height: 25px;
}
.container .item .del {
    position: absolute;
    top: 0;
    right: 0px;
    width: 40px;
    text-align: center;
    font-size: 40px;
    cursor: pointer;
    color: red;
}
.container .item .del:hover {
    background-color: orange;
}
</style>

祖传 app 组件

<div id="app">
    <div class="container">
        <my-cart></my-cart>
    </div>
</div>

实现基本布局

var CartTitle = {
    template: `
    	<div class="title">张三的商品</div>
    `
}

var CartList = {
    template: `
        <div>
            <div class="item">
            	<img src="img/a.png"/>
            <div class="name"></div>
            <div class="change">
            	<a href="">-</a>
            	<input type="text" class="num" />
            	<a href="">+</a>
            </div>
            	<div class="del">×</div>
            </div>
        </div>
    `
}

var CartTotal = {
    template: `
        <div class="total">
            <span>总价:{{ total }}</span>
            <button>结算</button>
        </div>
    `
}

Vue.component("my-cart",{
    template: `
        <div class="cart">
            <cart-title :uname="uname"></cart-title>
            	<cart-list :list="list"></cart-list>
            <cart-total :list="list"></cart-total>
        </div>
    `,
    components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
    }
})

const app = new Vue({
    el: "#app",
    data: {},
})

接下来要使用 my-cart 给各个子组件传递需要的内容

var CartTitle = {
    props: ["uname"],
    template: `
    	<div class="title">{{ uname }}的商品</div>
    `
}

var CartList = {
    props: ["list"],
    template: `
        <div>
            <div class="item" :key="item.id" v-for="item in list">
            	<img :src="item.img"/>
            <div class="name">{{ item.name }}</div>
            <div class="change">
            	<a href="">-</a>
            	<input type="text" class="num" />
            	<a href="">+</a>
            </div>
            	<div class="del">×</div>
            </div>
        </div>
    `
}

var CartTotal = {
    props: ["list"],
    template: `
        <div class="total">
            <span>总价:{{ total }}</span>
            <button>结算</button>
        </div>
    `,
    computed: {
        total: function(){
            // 计算商品的总价
            var t = 0
            this.list.filter((item)=>{	
                t += item.num * item.price
            })
            return t
        }
    }
}

Vue.component("my-cart",{
    data: function(){
        return {
            uname: "张三",
            list: [{
                id: 1,
                name: 'TCL彩电',
                price: 1000,
                num: 1,
                img: 'https://cdn.colorhub.me/imgsrv/d69c1836dbc86d7b0deb9256d6817d71ce8baeb1'
            },{
                id: 2,
                name: '机顶盒',
                price: 1000,
                num: 1,
                img: 'https://cdn.colorhub.me/imgsrv/0459b5ebcc9c7fd76c320f77dfc657c3305e6c71'
            },{
                id: 3,
                name: '海尔冰箱',
                price: 1000,
                num: 1,
                img: 'https://cdn.colorhub.me/imgsrv/670693a1f124395e9d3f0ca1f6993eac845cb6e1'
            },{
                id: 4,
                name: '小米手机',
                price: 1000,
                num: 1,
                img: 'https://cdn.colorhub.me/imgsrv/0e3eee5ee122361806bc95daa3a3c0951337c16b'
            },{
                id: 5,
                name: 'PPTV电视',
                price: 1000,
                num: 2,
                img: 'https://cdn.colorhub.me/imgsrv/6ab9f09d1b12a199084addd8b5b5253da6b0b861'
            }]
        }
    },
    template: `
        <div class="cart">
            <cart-title :uname="uname"></cart-title>
            <cart-list :list="list"></cart-list>
            <cart-total :list="list"></cart-total>
        </div>
    `,
    components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
    }
})

const app = new Vue({
    el: "#app",
    data: {},
})

接下来要实现删除按钮的功能

在项目开发中,在Vue中的父组件向子组件传递数据是通过 **v-bind:属性=“属性值”动态赋值,子组件通过props:{属性1,属性2,…}**接收传递过来的数据。

值得注意的是,Vue中的props传递是单向的,也就是说父子组件的prop之间形成的是一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行

这么做的原因是:防止在子组件中更改父级组件的状态

组件是可复用的,假设该子组件被复用了n次,当某一个组件意外地更改了其接受到的props数据 ----> 意味着,同时更改了父组件中相对应的属性值 ----> 意外地更改其它(n-1)个子组件中的 props数据,引起混乱

所以我们不应该在子组件中直接更改其props数据。

但有存在某些情况:必须对父组件传递过来的props进行转换处理,利用computed计算属性对数据进行处理。

<cart-list> 组件中的删除按钮添加删除事件,把对应的商品 id 传进去

<div class="del" @click="del(item.id)">×</div>

根据上面的原则,我们不能直接修改 props 传递过来的 list

然后把 id 传递给父组件,cart-del 是自定义的事件名称

methods: {
    del: function(id){
        // 把 id 传递给父组件
        this.$emit("cart-del", id)
    }  
}

然后在父组件中的 <cart-list> 子组件中使用 cart-del 事件,同样是自定义一个事件名,通过 $event 来接收子组件传递过来的 id

<div class="cart">
    <cart-title :uname="uname"></cart-title>
    <cart-list :list="list" @cart-del="delCart($event)"></cart-list>
    <cart-total :list="list"></cart-total>
</div>

然后通过父组件来删除对应的商品

methods: {
    delCart: function(id){
        var index = this.list.findIndex((item)=>{
            return item.id == id
        })
        this.list.splice(index, 1)
    }
}

在 input 中显示相应的商品数量

同样,我们会想用 v-model 来实现数量变化,同时去修改 list 列表,但是这样不行,因为根据以上的原则,不应该在子组件中直接更改其props数据,所以我直接去使用 :value 来获取商品的数量进行显示,然后通过绑定失去焦点的事件去父组件中动态修改 list 数据,如下

@blur - 失去焦点事件,里面传了两个参数

  • item.id - 触发该事件的商品 id
  • $event - 事件对象,固定名称
<input type="text" class="num" :value="item.num" @blur="changeNum(item.id, $event)" />

在methods 中定义该事件,把 id 和 变化之后的值传递给父组件,让父组件去真正的定义该事件

  • id - 商品id
  • num - 变化之后的值
changeNum: function(id, event){
    // console.log(id, event.target.value);
    this.$emit("change-num", {
        id: id,
        num: event.target.value
    })
}

在父组件中使用该自定义事件

<cart-list :list="list" @cart-del="delCart($event)" @change-num="changeNum($event)"></cart-list>

在父组件中定义该事件

changeNum: function(val){
    this.list.some((item)=>{
        if(item.id == val.id){
            item.num = val.num
            // 终止遍历
            return true
        }
    })
}

最后写一下加减商品数量的逻辑

<a href="" @click.prevent="sub(item.id)"></a>

<a href="" @click.prevent="add(item.id)"></a>

同样,不能在子组件中直接修改父组件中的数据

为了方便,我使用了相同名称的自定义组件,但是我为™添加了不同了 type 类型

changeNum: function(id, event){
    // console.log(id, event.target.value);
    this.$emit("change-num", {
        id: id,
        type: "change",
        num: event.target.value
    })
},
sub: function(id){
    this.$emit("change-num", {
        id: id,
        type: "sub",
        num: event.target.value
    })
},
add: function(id){
    this.$emit("change-num", {
        id: id,
        type: "add",
        num: event.target.value			  
    })
}

在父组件中使用

function(val){
    if(val.type == "change"){
        this.list.some((item)=>{
            if(item.id == val.id){
                item.num = val.num
                // 终止遍历
                return true
            }
        })
    }else if(val.type == "sub"){
        this.list.some((item)=>{
            if(item.id == val.id){
                item.num -= 1
                // 终止遍历
                return true
            }
        })
    }else if(val.type == "add"){
        this.list.some((item)=>{
            if(item.id == val.id){
                item.num += 1
                // 终止遍历
                return true
            }
        })
    }
}

实现效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码小余の博客

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

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

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

打赏作者

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

抵扣说明:

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

余额充值