vue+vant购物车功能+仿饿了么购物车抛物线效果
<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 => {//获取面值选中idif (item.active == true) {newArr.push(item.id)}// 获取购物车选中idif (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) {// 获取当前购物车选中商品idif (!this.selectId.includes(id)) {this.selectId.push(id); // 判断已选列表中是否存在该id,不是则追加进去} else {let index = this.selectId.indexOf(id); // 求出当前id的所在位置this.selectId.splice(index, 1); // 否则则删除}// 获取当前购物车所有商品idlet 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 = indexthis.allNumber(item)},// 选择面值切换toggleValue(item, itemson) {itemson.active = trueitemson.checked = trueif (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 = falsethis.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.leftthis.carY = carRect.topthis.carW = carRect.widthconst 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.heightconst x = this.carX + this.carW / 2 - btnRect.width / 2 - left, y = this.carY - btnRect.height - top + this.carW / 2 + btnRect.height / 2div.style.setProperty('--left', `${left}px`);div.style.setProperty('--top', `${top}px`);div.style.setProperty('--x', `${x}px`);div.style.setProperty('--y', `${y}px`);// 动画结束清除divdiv.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 = falsethis.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>*