一、效果
能够切换购买数量,切换规格组能够显示对应规格组图片(一张或者多张)
二、准备
项目中用到了 <u-popup> 组件,所以需要提前安装 uview-UI,安装方式请跳转官网:介绍 | uView 2.0 - 全面兼容 nvue 的 uni-app 生态框架 - uni-app UI 框架
三、组件代码
创建一个 sku.vue 文件保存以下代码(规格组 组件)
<template>
<!-- 规格组 -->
<u-popup :show="f_show" round="10" @close="toClosePopup">
<view class="popup">
<view class="popup-title">
选择规格
</view>
<view class="popup-check"
v-if="this.shopItemInfo&&this.selectArr&&this.shopItemInfo[this.selectArr.join(',')]">
<image class="popup-check-img" :src="imgBaseUrlId +shopItemInfo[selectArr.join(',')].image[0].id"
mode=""></image>
<view class="popup-check-name">
<view class="popup-check-name-sku">
选中规格: {{shopItemInfo[selectArr.join(',')].difference}}
</view>
<view class="price">
<view class="price-left">
<text>¥</text>
<text> {{ (Number(discountPrice)).toFixed(2)}}</text>
</view>
<view class="price-right">
<my-number-box :value.sync="count"></my-number-box>
</view>
</view>
</view>
</view>
<view class="" v-for="(item,index) in skuTagList" :key="index">
<view class="title">
{{item.name}}
</view>
<view class="box">
<view v-for="(i,n) in item.item" :key="n" @click="specificationBtn(i.name, index, i, n)" :class="[subIndex[index] == n ? 'active' : '',
!i.isShow ? 'disabled' : '',
]" class="box-item ">
{{i.name}}
</view>
</view>
</view>
<view class="btn" @click="toConfirm()">
确认
</view>
</view>
</u-popup>
</template>
<script>
import {
mapGetters
} from "vuex"
import myNumberBox from "@/pagesUser/components/numberBox.vue"
export default {
components: {
myNumberBox
},
props: {
// 组件显示
f_show: {
type: Boolean,
default: true
},
// 规格组信息
skusInfo: {
type: Array,
default: () => [],
},
// 规格标签
tagsInfo: {
type: Array,
default: () => [],
},
// 商品主图
coverId: {
type: String,
default: '',
},
// 商品名称
commodityName: {
type: String,
default: '',
},
},
data() {
return {
selectArr: [], // 存放已选中
shopItemInfo: {}, //存放要和选中的值进行匹配的数据
subIndex: [], //是否选中 因为不确定是多规格还是单规格,所以这里定义数组来判断
discountPrice: '', //价格
count: 1, //购买数量
skuTagList: [],
skuDifference: '', //规格组名称
skuImgId: '', //图片id
}
},
computed: {
...mapGetters(['imgBaseUrlId'])
},
watch: {
skusInfo: {
handler: function(newSkus, oldSkus) {
console.log("组件接收规格组数据", newSkus, oldSkus)
// 更新商品信息对象
newSkus.forEach(item => {
this.shopItemInfo[item.difference] = item;
});
// 查找最低价格商品
if (newSkus.length > 0) {
let minPrice = newSkus.reduce((min, curr) => (curr.discountPrice < min.discountPrice ? curr :
min), newSkus[0]);
this.selectArr = minPrice.difference.split(",");
// 获取需要的索引
this.selectArr.forEach(difference => {
this.tagsInfo.forEach(tag => {
const index = tag.item.findIndex(item => item.name === difference);
if (index !== -1) {
this.subIndex.push(index);
}
});
});
}
this.checkItem();
},
},
},
methods: {
// 点击确认
toConfirm() {
this.$util.judgeLogin().then(res => {
let data = {
name: this.commodityName,
coverId: this.coverId,
skuName: this.shopItemInfo[this.selectArr.join(',')].difference,
skuId: this.shopItemInfo[this.selectArr.join(',')].id,
skuPrice: this.shopItemInfo[this.selectArr.join(',')].originalPrice,
count: this.count
}
this.toClosePopup()
uni.navigateTo({
url: '/pagesUser/order/confirmOrder?data=' + JSON.stringify(data)
})
})
},
// 关闭弹窗
toClosePopup() {
this.$emit('update:f_show', false)
},
// 点击规格标签
specificationBtn(item, n, event, index) {
if (!event.isShow) return;
if (this.selectArr[n] != item) {
this.selectArr[n] = item;
this.subIndex[n] = index;
} else {
this.selectArr[n] = "";
this.subIndex[n] = -1; //去掉选中的颜色
}
let isOk = true;
isOk = this.selectArr.includes("") || this.selectArr.includes("empty");
if (this.selectArr.length == this.tagsInfo.length && !isOk) {
this.$emit("skuChange", this.selectArr.join(","));
} else {
console.log("不能回调");
}
this.checkItem();
},
checkItem() {
let self = this;
var option = [...self.tagsInfo];
var result = []; //定义数组储存被选中的值
for (var i in option) {
result[i] = self.selectArr[i] ? self.selectArr[i] : "";
}
for (var i in option) {
var last = result[i]; //把选中的值存放到字符串last去
for (var k in option[i].item) {
result[i] = option[i].item[k].name; //赋值,存在直接覆盖,不存在往里面添加name值
let isM = self.isMay(result, option[i].item[k].name)
self.$set(option[i].item[k], 'isShow', isM)
self.$forceUpdate()
}
result[i] = last; //还原,目的是记录点下去那个值,避免下一次执行循环时被覆盖
}
if (this.shopItemInfo[result]) {
this.originalPrice = this.shopItemInfo[result].originalPrice || "";
this.discountPrice = this.shopItemInfo[result].discountPrice || "";
}
// self.$forceUpdate(); //重绘
this.skuTagList = option
},
// 判断是否可以选择
isMay(result, name) {
for (var i in result) {
if (result[i] == "") {
return true; //如果数组里有为空的值,那直接返回true
}
}
if (this.shopItemInfo[result]) {
return true
} else {
return false
}
// return this.shopItemInfo[result] ?
// true :
// false; //匹配选中的数据的库存,若不为空返回true
},
}
}
</script>
<style lang="scss" scoped>
.popup {
padding: 30rpx 30rpx 0rpx 30rpx;
&-title {
text-align: center;
font-size: 28rpx;
font-weight: bold;
color: #555;
padding-bottom: 30rpx;
}
}
.price {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 20rpx;
&-left {
display: flex;
align-items: flex-end;
color: red;
&>text:first-child {
font-size: 26rpx;
}
&>text:last-child {
font-size: 34rpx;
font-weight: bold;
}
}
}
.title {
font-size: 26rpx;
color: #555;
font-weight: bold;
}
.box {
display: flex;
flex-wrap: wrap;
padding: 10rpx 0 30rpx 0;
&-item {
font-size: 28rpx;
// background: #f4f4f4;
padding: 14rpx 20rpx;
box-sizing: border-box;
border-radius: 8rpx;
margin: 0 20rpx 20rpx 0;
border: 1rpx solid #f4f4f4;
color: #666;
}
}
.btn {
margin-bottom: 30rpx;
height: 80rpx;
width: 100%;
border-radius: 40rpx;
font-size: 34rpx;
font-weight: bold;
color: #fff;
text-align: center;
line-height: 80rpx;
background: linear-gradient(to right, #598bff, #3c9cff)
}
.active {
border: 1rpx solid #2e8ef4;
color: #2e8ef4;
background: #fff;
}
.disabled {
background: #e7e7e9;
border: 1rpx solid #d0d0d2;
color: #999;
}
.popup-check {
padding-bottom: 30rpx;
display: flex;
align-items: center;
&-img {
height: 180rpx;
width: 180rpx;
margin-right: 20rpx;
border-radius: 10rpx;
flex-shrink: 0;
}
&-name {
width: 100%;
height: 180rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
&-sku {
font-size: 28rpx;
}
}
}
</style>
四、引入与使用
sku.vue 文件保存好以后就根据你的存放路径将它引入并注册。
然后就是对这个组件的使用和传参。
coverId:是我在组件中确认以后构造数据跳转进入“确认订单”页面时需要使用,如果你们在sku.vue组件内不做跳转(点击确认时用$emit()方法回调到页面来操作跳转),或者跳转时不需要这个coverId可以不用传。
commodityName: 是商品名称。同上也是我跳转传参需要。
f_show: 是双向绑定组件显示的餐宿,需要使用.sync语法糖,且在组件内改变该值需要使用this.$emit('update:f_show', “需要修改的值”)通知页面修改。
tagsInfo:规格标签数据。如果你想要直接使用的话需要保持以下结构:
skusInfo:规格组数据。如果你想要直接使用的话需要保持以下结构:
五、注意
1、tagsInfo和skusInfo是接口响应数据重新构造成的结构,你可以让后端接口返回的时候按这个结构返回或者返回结果你再重新整理一遍。
2、有一些参数是我在组件内部做跳转时需要,大家如果在跳转的时候用不到的话也可以不传。
3、目前我只实验了微信小程序和H5端能用,其他端还没测试(但是按道理应该也是没问题的)。
4、文章只是想记录一下自己在开发过程中的一些东西,如果能帮到大家我深感荣幸。如果有什么不足的地方还请批评和指正。