vue使用组件化思想实现一个简单的购物车页面

vue使用组件化思想实现一个简单的购物车页面

页面预览

在这里插入图片描述

项目结构

在这里插入图片描述

组件介绍

在这里插入图片描述

主页面ShopCar

  • 主要负责获取商品数据、渲染数据到相应组件
  • 将所有商品的选中状态传递给子组件Bottom
    在这里插入图片描述
  • 计算出已经勾选商品的总价格,将其传递给子组件Bottom
    在这里插入图片描述
  • 计算出已经勾选商品的总数量,将其传递给子组件Bottom

在这里插入图片描述

<template>
  <div class="wrap">
      <!-- 头部 -->
      <Header></Header>
      <!-- 商品栏 -->
      <Goods v-for="item in list" 
      :key="item.id" 
      :index="item.id" 
      :title="item.shopDesc" 
      :pic="item.imgSrc" 
      :price="item.price" 
      :status="item.status" 
      :count="item.num"
      @status-change="getNewStatus">
      </Goods>
      <div class="bottom">
          <Bottom :checked="allState" :allPrice="amount" :total="total" @all-change="getFullStatus" ></Bottom>
      </div>
  </div>
</template>
<script>
import Header from '../components/Header.vue';
import Goods from '../components/Goods.vue'
import Bottom from '../components/Bottom.vue';
import bus from '../components/eventBus'
export default {
    components:{
        Header,Bottom,Goods
    },
    data(){
        return{
           status:false,
            list:[
                {id:"01", status:true  ,imgSrc:'https://img.alicdn.com/imgextra/i4/14219353/O1CN0145jVEE2Ixj4WfGyOx_!!0-saturn_solar.jpg_468x468q75.jpg_.webp',shopDesc:'焕伊美宽松休闲F晒服外套女2022年新款夏季洋气百',price:'388.6',num:1},
                {id:"02", status:true,imgSrc:'https://img.alicdn.com/imgextra/i1/30603673/O1CN01AjV6XY1d0HKGpaEJt_!!0-saturn_solar.jpg_468x468q75.jpg_.webp',shopDesc:'七小铺潮牌设计感拼接条纹韩版卫衣女2022年春秋',price:'379.8',num:1},
                {id:"03", status:true,imgSrc:'https://img.alicdn.com/imgextra/i3/14219353/O1CN01ZKUntQ2Ixj4fJ4U1r_!!0-saturn_solar.jpg_468x468q75.jpg_.webp',shopDesc:'姿派女孩夏季镂空钩花遮晒衣女2022年新款夏装薄',price:'458.65',num:1},
                {id:"04", status:true,imgSrc:'https://img.alicdn.com/imgextra/i4/30301515/O1CN01qME9In1N3ultkxD54_!!0-saturn_solar.jpg_468x468q75.jpg_.webp',shopDesc:'汐颜洋气衬衣女时尚短袖雪纺衬衫2022春季新款女',price:'418.69',num:2},
                {id:"05", status:true,imgSrc:'https://img.alicdn.com/imgextra/i3/25431444/O1CN01NMvc931MXOZtzfxjN_!!0-saturn_solar.jpg_468x468q75.jpg_.webp',shopDesc:'NENW复古港味上衣韩版宽松白色衬衫女2022新款',price:'398.12',num:1}
            ]
        }
    },
    computed:{
        // 已勾选商品的总数量
        total(){
            return this.list.filter(item=> item.status).reduce((total,item)=>(total += item.num),0)
        },
        // 计算全选的状态
        allState(){
            return this.list.every(item =>item.status);
        },
        // 计算所有商品的价格
        amount(){
            return this.list.filter(item =>item.status).reduce((total,item)=>{
                return total += parseFloat(item.price) * parseInt(item.num);
            },0)
        }
    },
    methods:{
        // 接收子组件传递过来的全选状态
        getFullStatus(e){
            // console.log(e);
            this.list.forEach(item=>{
                item.status = e;
            })
        },
        // 接收子组件传递过来的数据
        getNewStatus(e){
            this.list.some(item =>{
                if(item.id == e.id){
                    item.status = e.value;
                }
            })
        }
    },
    created(){
        bus.$on('share',(val)=>{
            this.list.some(item =>{
                if(item.id === val.id){
                    item.num = val.value;
                    return true
                }
            })
        })
    }
}
</script>
<style lang="scss" scoped>
.wrap{
    text-align: left;
}
</style>

Header组件

<template>
  <div class="wrap">
      <div class="text">
          <span>购物车</span>
      </div>
  </div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.wrap{
    .text{
        width: 750px;
        height: 48px;
        background-color: rgb(12, 144, 239);
        text-align: center;
        margin: 0 auto;
        span{
            color: #fff;
            font-size: 18px;
            line-height: 48px;
            font-weight: bold;
        }
    }
}
</style>

Goods组件

  • 主要负责获取父组件ShopCar的数据,并动态渲染出来
    在这里插入图片描述
  • 勾选状态发生变化时通过自定义事件向父组件传值
    在这里插入图片描述
<template>
  <div class="wrap">
      <div class="goods-wrap">
          <!-- 左侧多选框 -->
          <div class="check">
              <input  type="checkbox" :id="index" :checked="status" @change="stastusChange">
              <label for="id"></label>
          </div>
            <!-- 右侧商品信息 -->
          <div class="goods-contain">
              <!-- 图片容器 -->
                    <div class="img-wrap">
                        <a href=""><img :src="pic" alt=""></a>
                    </div>
                    <!-- 商品描述 -->
                    <div class="shop-desc-wrap">
                        <!-- 商品描述 -->
                        <p class="desc">{{title}}</p>
                        <!-- 价格 -->
                        <p class="price">{{price}}</p>
                        <div class="button">
                            <Count :shopnum="count" :id="index"></Count>
                        </div>
                    </div>
          </div>  
      </div>
  </div>
</template>
<script>
import Count from './Count.vue';
export default {
    components:{
        Count,
    },
    props:{
        // 商品购买数量
        count:{
            type:Number,
            default:1,
        },
        // key
        index:{
            require:true,
            type:String
        },
        // 商品描述
        title:{
            default:'',
            type:String,
        },
        // 图片
        pic:{
            default:'',
            type:String,
        },
        // 商品价格
        price:{
            default:'0',
            type:String,
        },
        // 状态
        status:{
            default:true,
            type:Boolean
        }
    },
    data(){
        return{}
    },
    methods:{
        stastusChange(e){
            // console.log(e.target.id);
            // console.log(e.target.checked);
            this.$emit('status-change',{id:e.target.id,value:e.target.checked})
        }
    }
}
</script>

<style lang="scss" scoped>
.wrap{
        .goods-wrap{
        border-radius: 8px;
        display: flex;
        margin: 0 auto;
        width: 750px;
        height: 200px;
        margin: 8px auto;
        background-color: rgb(236, 230, 230);
        .check{
            width: calc(750px - 95%);
            line-height: 200px;
            text-align: center;
        }
        .goods-contain{
            width: 95%;
            height: 100%;
            display: flex;
            
            .img-wrap{
                width: 200px;
                height: 100%;
                display: flex;
                align-items: center;
                a{  
                    width: 200px;
                    height: 200px; 
                    overflow: hidden;
                }
                img{
                width: 200px;
                height: 200px; 
            }
            }
            .shop-desc-wrap{
                margin: 18px 0 0 12px;
                flex-grow: 1;
                position: relative;
                .desc{
                    font-size: 16px;
                    color: #333;
                }
                .price{
                    margin-top: 12px;
                    color: #f00;
                    font-size: 36px;
                    font-weight: bold;
                }
                .button{
                    position: absolute;
                    right: 22px;
                    bottom: 22px;
                }
            } 
        }
    }
}
</style>

Count组件

  • 负责对商品数量的控制
<template>
    <div class="button">
        <button @click="sub">-</button><span v-text="shopnum"></span><button @click="add">+</button>
    </div>
</template>
<script>
import bus from './eventBus';
export default {
    props:{
        //商品数量
        shopnum:{
            type:Number,
            default:1,
        },
        id:{
            require:true,
            type:String
        }
    },
    data(){
        return{

        }
    },
    methods:{
        add(){
            const obj = {id:this.id,value:this.shopnum +1}
            bus.$emit('share',obj);
        },
        sub(){
            if(this.shopnum -1 == 0){
                return
            }
            const obj = {id:this.id,value:this.shopnum -1}
            bus.$emit('share',obj);
        }
    }

}
</script>

<style lang="scss" scoped>
.button{
    display: flex;
    button{
        cursor: pointer;
        width: 34px;
        height: 30px;
        margin: 0 6px;
        font-size: 16px;
        background-color: transparent;
        border: none;
    }
    span{
        display: inline-block;
         height: 30px;
         width: 32px;
         line-height: 30px;
         text-align: center;
        background-color: #fff;
    }
    button:hover{
        background-color: rgb(227, 105, 105);
    }
    button:active{
        border: 1px solid #333;
        background-color: rgb(227, 105, 105);
    }
}
</style>

Bottom组件

  • 将选中商品的价格与数量动态计算后展示出来
  • 将全选的checked值传递给父元素
    在这里插入图片描述
<template>
  <div class="wrap">
      <!-- 全选 -->
      <div class="checkAll">
          <input type="checkbox" id="cheakAll" :checked="checked" @change="allchange">
          <label for="cheakAll">全选</label>
      </div>
      <!-- 价格 -->
      <div class="price">
        <span>合计:</span>
        <span>{{allPrice.toFixed(2)}}</span>
      </div>
      <!-- 结算按钮 -->
    <div class="submit">
        <span>结算({{total}})</span>
    </div>
  </div>
</template>

<script>
export default {
    props:{
        // 已勾选的商品数量
        total:{
            type:Number,
            default:0,
        },
        // 所有价格
        allPrice:{
            type:Number,
            default:0
        },
        // 全选状态
        checked:{
            type:Boolean,
            default:true
        }
    },
    data(){
        return{
        }
    },
    methods:{
        // 监听全选状态变化
        allchange(e){
            this.$emit('all-change',e.target.checked)
        }
    }
}
</script>
<style lang="scss" scoped>
.wrap{
    width: 750px;
    height: 58px;
    margin: 0 auto;
    background-color: rgb(206, 208, 210);
    display: flex;
    justify-content: space-around;
    align-items: center;
    font-size: 18px;
    font-weight: bold;
    .checkAll{
        input{
            width: 16px;
            height: 16px;
            border-radius: 50%;
        }
        label{
            text-align: middle;
        }
    }
    .price{
        span:last-child{
            color: red;
            font-size: 26px;
        }
    }
    .submit{
        width:120px;
        height: 48px;
        border-radius: 12px;
        text-align: center;
        line-height: 48px;
       background-color: rgb(28, 142, 187);
       cursor: pointer;
    }
    .submit:hover{
        background-color: rgb(11, 186, 255);
    }
    .submit:active{
        border: 1px solid rgb(0, 0, 0);
    }
}
</style>

eventBus.js

  • 里面就是一个vue示例,用于兄弟组件或者祖孙组件之间的通信
  • 数据发送方通过调用$emit()方法想接收方发送数据
  • 数据接收方通过调用$on()方法接收数据
  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

独立寒秋-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值