vue+vant购物车功能+仿饿了么购物车抛物线效果

这是一个使用Vue.js编写的商品页面模板,包含商品类型选择、面值选择、数量调整以及购物车功能。当用户选择商品类型和面值后,可以增加或减少数量,商品信息会实时更新到购物车。购物车功能包括显示已选商品、总价计算以及全选和单选选项。
摘要由CSDN通过智能技术生成

在这里插入图片描述

<template>
    <div>
        <ul class="ulMain">
            <li class="goodsMBox">
                <!-- 选择商品 s -->
                <div class="goods_option">
                    <ul class="goods_type" ref="ulType">
                        <li v-for="(item, index) in goodsType" :key="index" @click="radio = index, clickType(item, index)"
                            :class="{ 'js_goods_typeItem': radio == index, 'js_bottomRightRadius': radio - 1 == index, 'js_topRightRadius': radio + 1 == index }"
                            class="goods_typeItem js_clickTypeItem" ref="clickType">
                            {{ item.title }}
                            <p class="goods_typeTag" v-if="item.timeLimit && item.timeLimit != ''">{{ item.timeLimit }}
                            </p>
                        </li>
                    </ul>
                    <div class="goods_valueFace">
                        <p class="goods_valueFaceTitle">选择面值</p>
                        <div class="goods_valueFaceHeight">
                            <div class="goods_valueFaceBox" v-for="(item, index) in goodsType" :key="index"
                                v-show="radio == index">
                                <ul class="goods_mainFaceUl">
                                    <li v-for="(itemson, index) in item.faceVlaue" :key="index" class="goods_mainFaceLi"
                                        :class="{ goods_mainLiActive: itemson.active }"
                                        @click.stop="toggleValue(item, itemson)">
                                        <div class="goods_mainFaceLeft">
                                            <p class="goods_mainFace">{{ itemson.title }}</p>
                                            <p class="goods_mainVal">{{ itemson.price }}</p>
                                        </div>
                                        <div class="goods_mainFaceRight" @click.stop="clickAdd(item)">
                                            <span class="add_blue add_white" @click="toggleValue(item, itemson)"
                                                v-show="itemson.active == false"></span>
                                            <InputNumber v-show="itemson.active == true" :goodsType="goodsType"
                                                :radio="radio" :faceIndex="index" v-model="itemson.num"
                                                :max="itemson.maxNum" :click="true" @blurInput="blurInput" />
                                        </div>
                                        <!-- 直降限购一次 s-->
                                        <div class="goods_faceTag" v-if="itemson.maxNum == 1">
                                            惊爆价 限购1</div>
                                        <!-- 直降限购一次 e -->
                                    </li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- 选择商品 e -->
            </li>
        </ul>
        <!-- footer s -->
        <div class="goods_footer">
            <div class="goods_footerMain">
                <div class="goods_footerL">
                    <div class="goods_footerLBox js_goods_PriceShop" @click="clickShowShopping">
                        <img v-if="num > 0" class="goods_bag_blue" src="~assets/imgs/icon/bag_blue.svg" alt="">
                        <img v-else class="goods_bag_blue" src="~assets/imgs/icon/bag_gray.svg" alt="">
                        <!-- 为零隐藏 购物图标换成灰色 bag_gray.svg -->
                        <p v-if="num > 0" class="goods_PriceShopTag">{{ num }}</p>
                    </div>
                    <div class="goods_footerLText">
                        <p class="goods_footerLPrice">¥3400.68
                            <img src="~assets/imgs/kacn_vip1.png" alt="">
                        </p>
                        <p class="goods_footerLPrice2">普通会员:¥2900.66</p>
                    </div>
                </div>
                <div class="goods_footerBtn">
                    去结算
                </div>
            </div>
        </div>
        <!-- footer e -->
        <!-- 购物车 s -->
        <van-popup v-model="showShopping" round position="bottom">
            <div class="k_modelBox">
                <div class="shop_listBox" v-if="num > 0">
                    <div class="shop_checkAll">
                        <van-checkbox v-model="checkedAll" @click="clickCheckedAll">
                            <div class="shop_checkAllBox">
                                <p>购物清单</p>
                                <span>(已选{{ count }}种面值,共{{ num }}件)</span>
                            </div>
                        </van-checkbox>
                    </div>
                    <div class="shop_ul">
                        <van-checkbox class="shop_li" v-if="item.active" v-model="item.checked" :name="item.id"
                            @click="inputcheck(item.id, goodsType[radio].faceVlaue)"
                            v-for="(item, index) in goodsType[radio].faceVlaue" :key="index">
                            <div class="shop_checkBox">
                                <img class="shop_checkLogo" src="~assets/imgs/demo.jpg" alt="">
                                <div class="shop_checkText">
                                    <p class="shop_checkTitle"><span>{{ item.title }}</span></p>
                                    <p class="shop_checkPrice">{{ item.price }}</p>
                                </div>
                            </div>
                            <div class="goods_steBox shop_ste">
                                <label @click.stop="clickAdd(goodsType[radio])">
                                    <InputNumber class="shop_steBox" v-model="item.num" :click="false"
                                        :goodsType="goodsType" :radio="radio" :faceIndex="index" :max="item.maxNum"
                                        @blurInput="blurInput" />
                                </label>
                            </div>
                        </van-checkbox>
                    </div>
                </div>
                <!-- footer s -->
                <div class="shop_footer" v-if="num > 0">
                    <div class="goods_footerMain">
                        <div class="goods_footerL">
                            <div class="goods_footerLBox js_goods_PriceShop">
                                <img class="goods_bag_blue" src="~assets/imgs/icon/bag_blue.svg" alt="">
                                <!-- 为零隐藏 购物图标换成灰色 bag_gray.svg -->
                                <p class="goods_PriceShopTag">{{ num }}</p>
                            </div>
                            <div class="goods_footerLText">
                                <p class="goods_footerLPrice">¥3400.68
                                    <img src="~assets/imgs/kacn_vip1.png" alt="">
                                </p>
                                <p class="goods_footerLPrice2">普通会员:¥2900.66</p>
                            </div>
                        </div>
                        <div class="goods_footerBtn">
                            去结算
                        </div>
                    </div>
                </div>
                <!-- footer e -->
                <!-- 暂无 s -->
                <div class="default_text" v-if="num == 0">
                    暂无任何商品
                </div>
                <!-- 暂无 e -->
            </div>
        </van-popup>
        <!-- 购物车 e -->
    </div>
</template>
<script>
export default {
    name: 'goodsPage',
    data() {
        return {
            radio: 0, //当前选中
            num: 1, // 数量
            checkedAll: true, //全选
            selectId: [], //单选数组
            count: 0,//购物清单数量
            showShopping: false, //购物车
            "goodsType": [
                {
                    "title": "商品类型1",
                    "faceVlaue": [
                        {   
                            "id": "001",
                            "title": "商品1",
                            "price": "¥100.00",
                            "num": 1,
                            "maxNum": 999,
                            "active": true
                        },
                        {
                            "id": "002",
                            "title": "商品2",
                            "price": "¥300.00",
                            "num": 0,
                            "maxNum": 1,
                            "active": false
                        },
                        {
                            "id": "003",
                            "title": "商品3",
                            "price": "¥500.00",
                            "num": 0,
                            "maxNum": 10,
                            "active": false
                        }

                    ]
                },
                {
                    "title": "商品类型2",
                    "faceVlaue": [
                        {
                            "id": "001",
                            "title": "商品4",
                            "price": "¥100.00",
                            "num": 1,
                            "active": true
                        },
                        {
                            "id": "002",
                            "title": "商品5",
                            "price": "¥300.00",
                            "num": 0,
                            "active": false
                        },
                        {
                            "id": "003",
                            "title": "商品6",
                            "price": "¥500.00",
                            "num": 0,
                            "active": false
                        }
                    ]
                },
                {
                    "title": "商品类型3",
                    "timeLimit": "限时优惠",
                    "faceVlaue": [
                        {
                            "id": "001",
                            "title": "商品7",
                            "price": "¥100.00",
                            "num": 1,
                            "active": true
                        },
                        {
                            "id": "002",
                            "title": "商品8",
                            "price": "¥300.00",
                            "num": 0,
                            "active": false
                        },
                        {
                            "id": "003",
                            "title": "商品9",
                            "price": "¥500.00",
                            "num": 0,
                            "active": false
                        }
                    ]
                }
            ]
        }
    },
    mounted() {
        // 总数
        this.allNumber(this.goodsType[this.radio]);
        // 购物车默认选中
        this.goodsType.map(item => {
            item.faceVlaue.map(list => {
                if (list.active == true) {
                    list.checked = true;
                }
            })
        });
    },
    methods: {
        // 展开购物车
        clickShowShopping() {
            if (this.num > 0) {
                this.showShopping = true
            }
            let newArr = []
            this.selectId = []
            this.goodsType[this.radio].faceVlaue.map(item => {
                //获取面值选中id
                if (item.active == true) {
                    newArr.push(item.id)
                }
                // 获取购物车选中id
                if (item.checked == true) {
                    if (!this.selectId.includes(item.id)) {
                        this.selectId.push(item.id); // 判断已选列表中是否存在该id,不是则追加进去
                    }
                }
            })
            // 数组对比
            if (this.selectId.sort().toString() != newArr.sort().toString()) {
                this.checkedAll = false;
            } else {
                this.checkedAll = true;
            }
            this.count = this.selectId.length; //面值数量

            // console.log('购物车选中id', this.selectId);
            // console.log('面值选中id', newArr);
        },
        // 全选
        clickCheckedAll() {
            this.selectId = [];
            let idArrBox = [];
            // 为true 所有checkBox 选中
            if (this.checkedAll) {
                this.goodsType[this.radio].faceVlaue.map(item => {
                    if (item.active == true) {
                        item.checked = true;
                        this.selectId.push(item.id);
                        idArrBox.push(item.id);
                    }
                });
                // console.log('全选true', this.selectId);
            } else {
                // 为false 所有checkBox 取消选中
                this.goodsType[this.radio].faceVlaue.map(item => {
                    if (item.active == true) {
                        item.checked = false;
                    }
                    // console.log('全选false', this.selectId);
                });
            }
            this.count = this.selectId.length; //面值数量
        },
        // 单选
        inputcheck(id, allId) {
            // 获取当前购物车选中商品id
            if (!this.selectId.includes(id)) {
                this.selectId.push(id); // 判断已选列表中是否存在该id,不是则追加进去
            } else {
                let index = this.selectId.indexOf(id); // 求出当前id的所在位置
                this.selectId.splice(index, 1); // 否则则删除
            }
            // 获取当前购物车所有商品id
            let checkArr = [];
            allId.map(item => {
                if (item.active == true) {
                    checkArr.push(item.id);
                }
            });
            // 数组对比
            if (this.selectId.sort().toString() != checkArr.sort().toString()) {
                this.checkedAll = false;
            } else {
                this.checkedAll = true;
            }
            // console.log('selectId ', this.selectId);
            // console.log('checkArr ', checkArr);

            this.count = this.selectId.length; //面值数量
        },
        // 总数
        allNumber(item) {
            let numArr = []
            item.faceVlaue.forEach((i) => {
                numArr.push(i.num)
            })
            this.num = numArr.reduce((a, b) => a + b)
        },
        // 选择类型
        clickType(item, index) {
            this.radio = index
            this.allNumber(item)
        },
        // 选择面值切换
        toggleValue(item, itemson) {
            itemson.active = true
            itemson.checked = true
            if (itemson.num == 0) {
                itemson.num++
            }
            if (!this.selectId.includes(itemson.id)) {
                this.selectId.push(itemson.id); // 判断已选列表中是否存在该id,不是则追加进去
            }
            this.allNumber(item)
        },
        // 选择数量
        clickAdd(item) {
            this.allNumber(item)
        },
        // 计算数量
        blurInput() {
            this.allNumber(this.goodsType[this.radio])
        }

    },
}
</script>
<style scoped>
.goods_option {
    height: 100vh;
    display: flex;
}

.goods_type {
    width: 2rem;
    height: 100%;
    overflow-y: auto;
    background: white;
    display: flex;
    flex-direction: column;
}

.goods_typeItem {
    position: relative;
    box-sizing: border-box;
    padding: 0.3rem 0.2rem 0.3rem 0.24rem;
    font-size: 0.26rem;
    line-height: 0.4rem;
    background: #F5F5F5;
}

.js_goods_typeItem {
    background: white;
    border-radius: 0;
}

.goods_type::after {
    content: '';
    width: 2rem;
    display: block;
    background: #F5F5F5;
    flex: 1;
}

.goods_typeTag {
    padding: 0 0.1rem;
    height: 0.32rem;
    background: #EE0000;
    border-radius: 0.16rem 0.16rem 0.16rem 0px;
    position: absolute;
    top: 0;
    left: 0.5rem;
    line-height: 0.32rem;
    font-size: 0.2rem;
    color: white;
}

.goods_valueFace {
    width: 5.5rem;
    height: 9rem;
    background: white;
    padding: 0.3rem;
    box-sizing: border-box;
    border-radius: 0px 0px 0px 0.2rem;
}

.goods_valueFaceTitle {
    font-size: 0.28rem;
    line-height: 0.4rem;
}

.goods_valueFaceHeight {
    height: 8rem;
    overflow-y: auto;
}

.goods_mainFaceLi {
    width: 4.9rem;
    height: 1.4rem;
    background: #F5F5F5;
    border-radius: 0.12rem;
    display: flex;
    justify-content: space-between;
    padding: 0.2rem 0.3rem;
    box-sizing: border-box;
    cursor: pointer;
    margin-top: 0.2rem;
    position: relative;
    border: 0.02rem solid #DEE3EB;
}

.goods_mainLiActive {
    background: #E8F2FF !important;
    border: 0.02rem solid #3B8CFE !important;
}

.goods_mainFaceLeft {
    width: 2.5rem;
}

.goods_mainFace {
    font-size: 0.3rem;
    line-height: 0.6rem;
    font-weight: bold;
}

.goods_mainLiActive .goods_mainVal {
    color: #DA1414;
}

.goods_mainFaceRight {
    display: flex;
    align-items: center;
    justify-content: flex-end;
}

.add_blue {
    display: block;
    width: 0.4rem;
    height: 0.4rem;
    background: url(~assets/imgs/icon/num_add_blue.svg) no-repeat;
    background-size: 0.4rem;
}

.js_bottomRightRadius {
    border-bottom-right-radius: 0.2rem;
}

.js_topRightRadius {
    border-top-right-radius: 0.2rem;
}

.goods_faceTag {
    padding: 0 0.1rem;
    height: 0.32rem;
    line-height: 0.32rem;
    background: #EE0000;
    border-radius: 0px 0.12rem 0px 0.16rem;
    color: #FFFFFF;
    font-size: 0.2rem;
    position: absolute;
    top: -0.02rem;
    right: -0.02rem;
}

.goods_footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    z-index: 100;
}

.goods_footerMain {
    justify-content: space-between;
    display: flex;
    align-items: center;
    width: 100%;
    height: 1.2rem;
    background: white;
    display: flex;
    box-sizing: border-box;
    padding: 0 0.2rem 0 0.3rem;
}

.goods_footerL {
    display: flex;
}

.goods_footerLBox {
    position: relative;
}

.goods_bag_blue {
    width: 0.8rem;
    height: 0.8rem;
    border-radius: 0.4rem;
}

.goods_PriceShopTag {
    min-width: 0.32rem;
    padding: 0 0.08rem;
    background: #EE0000;
    border-radius: 0.16rem;
    height: 0.32rem;
    line-height: 0.32rem;
    position: absolute;
    top: -0.06rem;
    right: -0.06rem;
    text-align: center;
    color: #FFFFFF;
    box-sizing: border-box;
}

.goods_footerLText {
    margin-left: 0.2rem;
}

.goods_footerLPrice {
    color: #EE0000;
    display: flex;
    box-align: center;
    align-items: center;
    font-weight: bold;
    height: 0.48rem;
    font-size: 0.32rem;
}

.goods_footerLPrice img {
    width: 0.64rem;
    height: 0.24rem;
}

.goods_footerLPrice2 {
    color: #666666;
    line-height: 0.36rem;
}

.goods_footerBtn {
    width: 2rem;
    height: 0.8rem;
    background: linear-gradient(270deg, #249BFF 0%, #2B6AFF 100%);
    box-shadow: 0px 0.08rem 0.2rem 0px rgba(0, 107, 255, 0.4);
    border-radius: 0.4rem;
    color: #FFFFFF;
    font-weight: bold;
    font-size: 0.32rem;
    text-align: center;
    line-height: 0.8rem;
}

.k_modelBox {
    height: 10rem;
    background: #F5F5F5;
    position: relative;
}

.shop_checkAll {
    display: flex;
    box-align: center;
    align-items: center;
    height: 1rem;
    font-size: 0.32rem;
    line-height: 0.4rem;
    font-weight: bold;
    box-sizing: border-box;
    border-bottom: 0.02rem solid #DEE3EB;
    width: 6.9rem;
    margin: 0 auto;
}

.shop_checkAllBox {
    margin-left: 0.2rem;
    display: flex;
}

.shop_checkAll span {
    color: #999999;
    font-size: 0.28rem;
}

.shop_ul {
    height: 7.8rem;
    overflow-y: auto;
    padding-top: 0.3rem;
    box-sizing: border-box;
}

.shop_li {
    width: 6.9rem;
    margin: 0 auto;
    margin-bottom: 0.6rem;
    box-sizing: border-box;
    height: 1.2rem;
    position: relative;
}

.shop_checkBox {
    margin-left: 0.2rem;
    display: flex;
    box-align: center;
    align-items: center;
}

.shop_checkLogo {
    width: 1.2rem;
    height: 1.2rem;
    border-radius: 0.16rem;
    margin-right: 0.2rem;
}

.shop_checkText {
    height: 1.2rem;
}

.shop_checkTitle {
    font-weight: bold;
    font-size: 0.28rem;
    line-height: 0.4rem;
}

.shop_checkPrice {
    font-weight: bold;
    font-size: 0.28rem;
    line-height: 0.4rem;
    color: #EE0000;
    margin-top: 0.4rem;
}

.shop_ste {
    position: absolute;
    right: 0;
    bottom: 0;
}

.default_text {
    text-align: center;
    line-height: 10rem;
    font-size: 0.4rem;
}
</style>

步进器组件:

<template>
    <div class="count">
        <label>
            <span class="reduce" @click="reduce"></span>
        </label>
        <input type="number" onKeypress="return (/[\d]/.test(String.fromCharCode(event.keyCode)))" @keyup.enter="keyEnter"
            v-model="num" @change="change" @blur="blurInput">
        <label>
            <span v-if="max > 1" class="plus" ref="btn" @click="plus"></span>
            <span v-else class="maxNum"></span>
        </label>
    </div>
</template>
 
<script>
export default {
    name: "InputNumber",
    data() {
        return {
            num: '',
            carX: 0,//购物车坐标
            carY: 0,//购物车坐标
            carW: 0,//购物车宽度
        }
    },
    props: {
        value: {
            type: Number,
            default: 1
        },
        min: {
            type: Number,
            default: 0
        },
        max: {
            type: Number,
            default: 999
        },
        goodsType: {
            type: Array,
            default: () => []
        },
        radio: {
            type: Number,
            default: 0
        },
        faceIndex: {
            type: Number,
            default: 0
        },
        click: {
            type: Boolean,
            default: true
        },
    },
    created() {
        this.inspect();
    },
    mounted() {

    },
    methods: {
        reduce() {
            if (this.num > this.min) {
                this.num--;
                this.$emit('input', this.num);
            }
            if (this.num == 0) {
                setTimeout(() => {
                    this.goodsType[this.radio].faceVlaue[this.faceIndex].active = false
                    this.goodsType[this.radio].faceVlaue[this.faceIndex].checked = false
                }, 0);
            }
        },
        plus() {
            if (this.num < this.max) {
                this.num++;
                this.$emit('input', this.num);
            }
            if (this.value >= this.max) {
                this.$toast('超出购买数量');
            }
            this.clickPlus()
        },
        clickPlus() {
            if (this.click && this.max > this.value) {
                // 获取购物车位置信息
                const carRect = document.querySelector('.js_goods_PriceShop').getBoundingClientRect()
                this.carX = carRect.left
                this.carY = carRect.top
                this.carW = carRect.width
                const div = document.createElement('div')
                div.className = 'add'
                div.innerHTML = ` <span class="iconAdd"></span>`
                document.body.appendChild(div)
                // 获取初始位置信息
                const btnRect = this.$refs.btn.getBoundingClientRect()
                const left = btnRect.left, top = btnRect.top - btnRect.height
                const x = this.carX + this.carW / 2 - btnRect.width / 2 - left, y = this.carY - btnRect.height - top + this.carW / 2 + btnRect.height / 2
                div.style.setProperty('--left', `${left}px`);
                div.style.setProperty('--top', `${top}px`);
                div.style.setProperty('--x', `${x}px`);
                div.style.setProperty('--y', `${y}px`);
                // 动画结束清除div
                div.addEventListener('animationend', () => {
                    div.remove()
                })
            }
        },
        change() {
            if (this.num > this.max) {
                this.num = this.max;
                this.$toast('超出购买数量');
            }
            if (this.num < this.min) this.num = this.min;
        },
        // 计算数量 失去焦点
        blurInput() {
            this.goodsType[this.radio].faceVlaue[this.faceIndex].num = Number(this.num)
            this.$emit('blurInput')
            if (this.num == 0) {
                this.goodsType[this.radio].faceVlaue[this.faceIndex].active = false
                this.goodsType[this.radio].faceVlaue[this.faceIndex].checked = false
            } else {
                this.clickPlus()
            }
        },
        keyEnter(e) {
            e.srcElement.blur(); // 让输入框主动失焦
        },
        inspect() {
            //判断用户传递的传递的初始值是否合规
            if (this.value > this.max) {
                this.num = this.max
            } else if (this.value < this.min) {
                this.num = this.min
            } else {
                this.num = this.value
            }
        }
    },
    watch: {
        value(newVal) {
            this.num = newVal
        }
    }
}
</script>
 
<style lang="less">
.count {
    width: 1.6rem;
    height: 0.4rem;
    line-height: 0.4rem;
    .flex;
    cursor: pointer;
}

.count label {
    cursor: pointer;
}

.count .reduce {
    width: 0.4rem;
    height: 0.4rem;
    background: url(~assets/imgs/icon/num_reduce_hover.svg) no-repeat center;
    background-size: 0.4rem 0.4rem;
    display: block;
}

.count .plus {
    width: 0.4rem;
    height: 0.4rem;
    background: url(~assets/imgs/icon/num_add_hover.svg) no-repeat center;
    background-size: 0.4rem 0.4rem;
    display: block;
}

.count .maxNum {
    width: 0.4rem;
    height: 0.4rem;
    background: url(~assets/imgs/icon/num_add_gray.svg) no-repeat center;
    background-size: 0.4rem 0.4rem;
    display: block;
    cursor: no-drop;
    // pointer-events: none;
}

.count input {
    display: inline-block;
    width: 0.8rem;
    height: 0.4rem;
    border: none;
    box-sizing: border-box;
    text-align: center;
    font-size: 0.32rem;
    color: #333;
    background: transparent;
    outline: none;
}

input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
    -webkit-appearance: none !important;
}

/* chrome */

input[type="number"] {
    -moz-appearance: textfield;
}

/* firefox */
.add {
    position: fixed;
    left: var(--left);
    top: var(--top);
    z-index: 9999;
}

.iconAdd {
    display: block;
    width: 0.4rem;
    height: 0.4rem;
    background: url(~assets/imgs/icon/num_add_hover.svg) no-repeat center;
    background-size: 0.4rem 0.4rem;
}

@keyframes moveY {
    to {
        transform: translateY(var(--y));
    }
}

.add {
    animation: moveY 0.5s cubic-bezier(0.5, -0.5, 1, 1);
}

@keyframes moveX {
    to {
        transform: translateX(var(--x));
    }
}

.iconAdd {
    animation: moveX 0.5s linear;
}
</style>*  
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值