<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 = ` `
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>*