1.vue代码
1.1 html代码
//html
<div class="sku">
<h3>iphone 13</h3>
<div style="margin-bottom:10px;" v-for="(attr, index) in process_attribute" :key="index">
<h4>{{ attr.name }}</h4>
<div class="sku-group">
<button
v-for="(item, index2) in attr.item"
:key="index2"
style="margin: 0 5px 5px 0;"
:style="{
color:item.actived ? 'red' : '#666',
opacity:item.disabled?0.7:1
}"
:disabled="item.disabled"
@click="skuClick(index, index2)">{{ item.name }}</button>
</div>
</div>
<div style="margin-top: 20px;">
库存:
<span>{{ stock }}</span>
</div>
<div style="margin-bottom:20px;">
价格:
<span v-if="minprice == maxprice">{{ minprice }}</span>
<span v-else>{{ minprice }} - {{ maxprice }}</span>
</div>
<button type="button" @click="submit">购买</button>
</div>
//js
1.2 js代码
//data参数
data() {
return {
separator: ';',
attribute: [{
"name": "颜色",
"item": [
"星光色",
"午夜色",
"粉色",
"蓝色",
"红色"
]
},
{
"name": "版本",
"item": [
"128GB",
"256GB",
"512GB"
]
},
{
"name": "运营商",
"item": [
"全网通",
"移动版",
"联通版",
"电信版"
]
}
],
sku: [{
"sku": "星光色;128GB;全网通",
"price": 5699,
"stock": 0
},
{
"sku": "星光色;128GB;移动版",
"price": 5699,
"stock": 0
},
{
"sku": "星光色;128GB;联通版",
"price": 5699,
"stock": 0
},
{
"sku": "星光色;128GB;电信版",
"price": 5699,
"stock": 0
},
{
"sku": "星光色;256GB;全网通",
"price": 7288,
"stock": 0
},
{
"sku": "星光色;256GB;电信版",
"price": 7288,
"stock": 0
},
{
"sku": "星光色;512GB;全网通",
"price": 9688,
"stock": 0
},
{
"sku": "星光色;512GB;移动版",
"price": 9688,
"stock": 0
},
{
"sku": "星光色;512GB;联通版",
"price": 9688,
"stock": 0
},
{
"sku": "星光色;512GB;电信版",
"price": 9688,
"stock": 0
},
{
"sku": "午夜色;128GB;全网通",
"price": 5699,
"stock": 0
},
{
"sku": "午夜色;128GB;移动版",
"price": 5695,
"stock": 10
},
{
"sku": "午夜色;128GB;联通版",
"price": 5699,
"stock": 10
},
{
"sku": "午夜色;128GB;电信版",
"price": 5699,
"stock": 0
},
{
"sku": "午夜色;256GB;全网通",
"price": 7288,
"stock": 10
},
{
"sku": "午夜色;256GB;移动版",
"price": 7288,
"stock": 10
},
{
"sku": "午夜色;256GB;联通版",
"price": 7288,
"stock": 10
},
{
"sku": "午夜色;256GB;电信版",
"price": 7288,
"stock": 0
},
{
"sku": "午夜色;512GB;全网通",
"price": 9688,
"stock": 10
},
{
"sku": "午夜色;512GB;移动版",
"price": 9688,
"stock": 10
},
{
"sku": "午夜色;512GB;联通版",
"price": 9688,
"stock": 10
},
{
"sku": "午夜色;512GB;电信版",
"price": 9688,
"stock": 0
},
{
"sku": "粉色;128GB;全网通",
"price": 5699,
"stock": 10
},
{
"sku": "粉色;128GB;移动版",
"price": 5699,
"stock": 10
},
{
"sku": "粉色;128GB;联通版",
"price": 5699,
"stock": 10
},
{
"sku": "粉色;128GB;电信版",
"price": 5699,
"stock": 0
},
{
"sku": "粉色;256GB;全网通",
"price": 7288,
"stock": 10
},
{
"sku": "粉色;256GB;移动版",
"price": 7288,
"stock": 10
},
{
"sku": "粉色;256GB;联通版",
"price": 7288,
"stock": 10
},
{
"sku": "粉色;256GB;电信版",
"price": 7288,
"stock": 0
},
{
"sku": "粉色;512GB;全网通",
"price": 9688,
"stock": 10
},
{
"sku": "粉色;512GB;移动版",
"price": 9688,
"stock": 10
},
{
"sku": "粉色;512GB;联通版",
"price": 9688,
"stock": 10
},
{
"sku": "粉色;512GB;电信版",
"price": 9688,
"stock": 0
},
{
"sku": "蓝色;128GB;全网通",
"price": 5699,
"stock": 10
},
{
"sku": "蓝色;128GB;移动版",
"price": 5699,
"stock": 10
},
{
"sku": "蓝色;128GB;联通版",
"price": 5699,
"stock": 10
},
{
"sku": "蓝色;128GB;电信版",
"price": 5699,
"stock": 0
},
{
"sku": "蓝色;256GB;全网通",
"price": 7288,
"stock": 10
},
{
"sku": "蓝色;256GB;移动版",
"price": 7288,
"stock": 10
},
{
"sku": "蓝色;256GB;联通版",
"price": 7288,
"stock": 10
},
],
process_attribute: [],
process_sku: [],
// 当前选中 sku 的库存及价格区间
stock: '',
minprice: '',
maxprice: ''
}
},
//方法和渲染
mounted() {
this.init()
},
methods: {
init() {
// 对 attribute 数据进行加工,并存入 process_attribute 中
this.attribute.map(attr => {
let temp = {
name: attr.name
}
temp.item = attr.item.map(item => {
return {
name: item,
actived: false,
disabled: this.itemquantity(item) <= 0
}
})
this.process_attribute.push(temp)
})
// 对 sku 数据进行加工,并存入 process_sku 中
this.sku.map(v => {
var combArr = this.arrayCombine(v.sku.split(this.separator))
for (var j = 0; j < combArr.length; j++) {
var key = combArr[j].join(this.separator)
if (this.process_sku[key]) {
// 库存累加,价格添加进数组
this.process_sku[key].stock += v.stock
this.process_sku[key].prices.push(v.price)
} else {
this.process_sku[key] = {
stock: v.stock,
prices: [v.price]
}
}
}
})
// 更新数据视图
this.process_sku = Object.assign({}, this.process_sku)
this.skuCheck()
console.log('process_attribute', this.process_attribute);
console.log('process_sku', this.process_sku);
},
//查询属性数量
itemquantity(item) {
let quantity = 0;
this.sku.forEach(element => {
var skuArr = element.sku.split(this.separator);
if (skuArr.indexOf(item) != -1) {
quantity += element.stock;
}
});
return quantity
},
arrayCombine(targetArr) {
var resultArr = []
for (var n = 0; n <= targetArr.length; n++) {
var flagArrs = this.getFlagArrs(targetArr.length, n)
while (flagArrs.length) {
var flagArr = flagArrs.shift()
var combArr = Array(targetArr.length)
for (var i = 0; i < targetArr.length; i++) {
if (flagArr[i]) {
combArr[i] = targetArr[i]
}
}
resultArr.push(combArr)
}
}
return resultArr
},
getFlagArrs(m, n) {
var flagArrs = [],
flagArr = [],
isEnd = false
for (var i = 0; i < m; i++) {
flagArr[i] = i < n ? 1 : 0
}
flagArrs.push(flagArr.concat())
// 当n不等于0并且m大于n的时候进入
if (n && m > n) {
while (!isEnd) {
var leftCnt = 0
for (var i = 0; i < m - 1; i++) {
if (flagArr[i] == 1 && flagArr[i + 1] == 0) {
for (var j = 0; j < i; j++) {
flagArr[j] = j < leftCnt ? 1 : 0
}
flagArr[i] = 0
flagArr[i + 1] = 1
var aTmp = flagArr.concat()
flagArrs.push(aTmp)
if (aTmp.slice(-n).join('').indexOf('0') == -1) {
isEnd = true
}
break
}
flagArr[i] == 1 && leftCnt++
}
}
}
return flagArrs
},
skuClick(key1, key2) {
if (!this.process_attribute[key1].item[key2].disabled) {
this.process_attribute[key1].item.map((item, index) => {
item.actived = index == key2 ? !item.actived : false
})
this.skuCheck()
this.getStockPrice()
}
},
skuCheck() {
let sku = []
this.process_attribute.map(attr => {
let name = ''
attr.item.map(item => {
if (item.actived) {
name = item.name
}
})
sku.push(name)
})
this.stock = this.process_sku[sku.join(this.separator)].stock
this.minprice = Math.min.apply(Math, this.process_sku[sku.join(this.separator)].prices)
this.maxprice = Math.max.apply(Math, this.process_sku[sku.join(this.separator)].prices)
},
getStockPrice() {
this.process_attribute.map(attr => {
attr.item.map(item => {
item.disabled = this.itemquantity(item.name) <= 0
})
})
let count = 0
let i = 0
this.process_attribute.map((attr, index) => {
let flag = false
attr.item.map(item => {
if (item.actived) {
flag = true
}
})
if (!flag) {
count += 1
i = index
}
})
// 当只有一组规格没选时
if (count == 1) {
this.process_attribute[i].item.map(item => {
let sku = []
let text = item.name
this.process_attribute.map((attr, index) => {
if (index != i) {
attr.item.map(item2 => {
if (item2.actived) {
sku.push(item2.name)
}
})
} else {
sku.push(text)
}
})
if (this.process_sku[sku.join(this.separator)].stock == 0) {
item.disabled = true
}
})
}
// 当所有规格都有选时
if (count == 0) {
this.process_attribute.map((attr, index) => {
let i = index
this.process_attribute[index].item.map(item => {
if (!item.actived) {
let sku = []
let text = item.name
this.process_attribute.map((list, index) => {
if (index != i) {
list.item.map(item2 => {
if (item2.actived) {
sku.push(item2.name)
}
})
} else {
sku.push(text)
}
})
if (this.process_sku[sku.join(this.separator)].stock == 0) {
item.disabled = true
}
}
})
})
}
},
submit() {
let sku = []
let isSelectSKU = this.process_attribute.every(attr => {
let filter = attr.item.filter(v => v.actived)
if (filter.length != 0) {
sku.push(filter[0].name)
}
return filter.length != 0
})
if (isSelectSKU) {
alert(`当前SKU为:${sku.join(this.separator)}`)
} else {
alert('请选择完整商品属性')
}
}
}
2.小程序代码
2.1 小程序wxml
<view class="product_type_dia"></view>
<view class="wait_appoint_box active">
<view class="wait_appoint_main">
<view class="wait_appoint_main_top">
<image class="image_icon_product" mode="aspectFill" src="https://qn.teckongroup.com/fcbd2eb7ccd8b45350d1f1794278c8dd.jpg-b65ccc46-58c5-498b-99e6-1dd830ad8c76?e=3205907883&token=E5awrJ_kA5QMsl2SZRitj11DfZ7uInaqjqhlFajV:mqXBRk7Qu3XTKTtjOQPImO1rmrM=">
</image>
<view class="product_type_dia_money">
<view class="top" wx:if="{{minprice != maxprice}}">¥{{minprice}}- {{ maxprice }}</view>
<view class="top" wx:if="{{minprice == maxprice}}">¥{{minprice}}</view>
<view class="bottom">库存{{stock}}件</view>
</view>
<image src="/images/icon_close.png" class="icon_close_size"></image>
</view>
<block wx:for="{{process_attribute}}" wx:for-index="index" wx:for-item="attr">
<view class="wait_appoint_main_type">{{attr.name}}</view>
<view class="wait_appoint_main_type_list">
<view class="wait_appoint_main_type_item {{item.actived?'active':''}}" style="color:{{item.disabled?'#C3C3C3':''}}" wx:for="{{attr.item}}" bindtap="skuClick" data-index="{{index}}" data-index2="{{index2}}" wx:for-index="index2">
{{item.name}}
</view>
</view>
</block>
<view class="wait_appoint_main_type_number">
<view class="wait_appoint_main_type_left">数量</view>
<view class="cart_item_cart_counter">
<view class="dec" bindtap="toReduce">-</view>
<input type="number" class="graty" value="{{quantity}}" disabled />
<view class="dec" bindtap="toAdd">+</view>
</view>
</view>
<view class="dia_btn_box">
<view class="dia_btn_red_active">加入购物车</view>
<view class="dia_btn_yellow_active" bindtap="toBuyThisGoods">立即购买</view>
</view>
</view>
</view>
2.2 小程序js
// components/bannar/banner.js
Component({
/**
* 组件的属性列表
*/
properties: {
banner: {
type: Object, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
value: [], // 属性初始值(可选),如果未指定则会根据类型选择一个
},
attribute: {
type: Array, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
value: [],
},
sku: {
type: Array, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
value: [],
}
},
/**
* 组件的初始数据
*/
data: {
process_attribute: [],
process_sku: [],
// 当前选中 sku 的库存及价格区间
stock: '',
minprice: '',
separator: ';',
maxprice: '',
quantity:1
},
/**
* 组件的方法列表
*/
methods: {
init() {
// 对 attribute 数据进行加工,并存入 process_attribute 中
this.data.attribute.map(attr => {
let temp = {
name: attr.name
}
temp.item = attr.item.map(item => {
return {
name: item,
actived: false,
disabled: this.itemquantity(item) <= 0
}
})
this.data.process_attribute.push(temp)
})
// console.log(this.data.process_attribute)
this.setData({
process_attribute:this.data.process_attribute
})
// 对 sku 数据进行加工,并存入 process_sku 中
this.data.sku.map(v => {
var combArr = this.arrayCombine(v.sku.split(this.data.separator))
for (var j = 0; j < combArr.length; j++) {
var key = combArr[j].join(this.data.separator)
if (this.data.process_sku[key]) {
// 库存累加,价格添加进数组
this.data.process_sku[key].stock += v.stock
this.data.process_sku[key].prices.push(v.price)
} else {
this.data.process_sku[key] = {
stock: v.stock,
prices: [v.price]
}
}
}
})
// 更新数据视图
this.data.process_sku = Object.assign({}, this.data.process_sku)
console.log('process_sku', this.data.process_sku);
this.skuCheck()
console.log('process_attribute', this.data.process_attribute);
},
//查询属性数量
itemquantity(item) {
let quantity = 0;
this.data.sku.forEach(element => {
var skuArr = element.sku.split(this.data.separator);
if (skuArr.indexOf(item) != -1) {
quantity += element.stock;
}
});
return quantity
},
arrayCombine(targetArr) {
var resultArr = []
for (var n = 0; n <= targetArr.length; n++) {
var flagArrs = this.getFlagArrs(targetArr.length, n)
while (flagArrs.length) {
var flagArr = flagArrs.shift()
var combArr = Array(targetArr.length)
for (var i = 0; i < targetArr.length; i++) {
if (flagArr[i]) {
combArr[i] = targetArr[i]
}
}
resultArr.push(combArr)
}
}
return resultArr
},
getFlagArrs(m, n) {
var flagArrs = [],
flagArr = [],
isEnd = false
for (var i = 0; i < m; i++) {
flagArr[i] = i < n ? 1 : 0
}
flagArrs.push(flagArr.concat())
// 当n不等于0并且m大于n的时候进入
if (n && m > n) {
while (!isEnd) {
var leftCnt = 0
for (var i = 0; i < m - 1; i++) {
if (flagArr[i] == 1 && flagArr[i + 1] == 0) {
for (var j = 0; j < i; j++) {
flagArr[j] = j < leftCnt ? 1 : 0
}
flagArr[i] = 0
flagArr[i + 1] = 1
var aTmp = flagArr.concat()
flagArrs.push(aTmp)
if (aTmp.slice(-n).join('').indexOf('0') == -1) {
isEnd = true
}
break
}
flagArr[i] == 1 && leftCnt++
}
}
}
return flagArrs
},
skuClick(e) {
let key1 = e.currentTarget.dataset.index
let key2 = e.currentTarget.dataset.index2
if (!this.data.process_attribute[key1].item[key2].disabled) {
this.data.process_attribute[key1].item.map((item, index) => {
item.actived = index == key2 ? !item.actived : false
})
this.skuCheck()
this.getStockPrice()
this.setData({
process_attribute: this.data.process_attribute
})
}
},
skuCheck() {
let sku = []
this.data.process_attribute.map(attr => {
let name = ''
attr.item.map(item => {
if (item.actived) {
name = item.name
}
})
sku.push(name)
})
this.data.stock = this.data.process_sku[sku.join(this.data.separator)].stock
this.data.minprice = Math.min.apply(Math, this.data.process_sku[sku.join(this.data.separator)].prices)
this.data.maxprice = Math.max.apply(Math, this.data.process_sku[sku.join(this.data.separator)].prices)
this.setData({
minprice:this.data.minprice ,
maxprice:this.data.maxprice ,
stock:this.data.stock
})
},
getStockPrice() {
this.data.process_attribute.map(attr => {
attr.item.map(item => {
item.disabled = this.itemquantity(item.name) <= 0
})
})
let count = 0
let i = 0
this.data.process_attribute.map((attr, index) => {
let flag = false
attr.item.map(item => {
if (item.actived) {
flag = true
}
})
if (!flag) {
count += 1
i = index
}
})
// 当只有一组规格没选时
if (count == 1) {
this.data.process_attribute[i].item.map(item => {
let sku = []
let text = item.name
this.data.process_attribute.map((attr, index) => {
if (index != i) {
attr.item.map(item2 => {
if (item2.actived) {
sku.push(item2.name)
}
})
} else {
sku.push(text)
}
})
if (this.data.process_sku[sku.join(this.data.separator)].stock == 0) {
item.disabled = true
}
})
}
// 当所有规格都有选时
if (count == 0) {
this.data.process_attribute.map((attr, index) => {
let i = index
this.data.process_attribute[index].item.map(item => {
if (!item.actived) {
let sku = []
let text = item.name
this.data.process_attribute.map((list, index) => {
if (index != i) {
list.item.map(item2 => {
if (item2.actived) {
sku.push(item2.name)
}
})
} else {
sku.push(text)
}
})
if (this.data.process_sku[sku.join(this.data.separator)].stock == 0) {
item.disabled = true
}
}
})
})
}
},
submit() {
let sku = []
let isSelectSKU = this.data.process_attribute.every(attr => {
let filter = attr.item.filter(v => v.actived)
if (filter.length != 0) {
sku.push(filter[0].name)
}
return filter.length != 0
})
if (isSelectSKU) {
alert(`当前SKU为:${sku.join(this.data.separator)}`)
} else {
alert('请选择完整商品属性')
}
}
},
ready() {
console.log(this.data.attribute)
this.init()
}
})
2.3 小程序样式
.product_type_dia {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: #000000;
opacity: 0.5;
z-index: 100;
}
.wait_appoint_box {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 1003;
transform: translateY(100%);
/* transform: translateY(0); */
transition: all 0.3s;
padding: 28rpx 0;
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
}
.wait_appoint_box.active {
transform: translateY(0);
}
.wait_appoint_main {
margin: 0 24rpx;
}
.wait_appoint_main_top {
display: flex;
align-items: flex-end;
margin-bottom: 60rpx;
}
.image_icon_product {
width: 180rpx;
height: 180rpx;
border-radius: 14rpx;
margin-right: 30rpx;
}
.product_type_dia_money {
flex: 1;
}
.product_type_dia_money .top {
font-size: 24rpx;
font-weight: 600;
color: #F23030;
margin-bottom: 16rpx;
}
.product_type_dia_money .bottom {
font-size: 22rpx;
font-weight: 400;
color: #999999;
}
.icon_close_size {
width: 36rpx;
height: 36rpx;
position: absolute;
top: 26rpx;
right: 32rpx;
}
.wait_appoint_main_type {
font-size: 26rpx;
font-weight: 600;
color: #333333;
margin-bottom: 30rpx;
}
.wait_appoint_main_type_left {
font-size: 26rpx;
font-weight: 600;
color: #333333;
flex: 1;
}
.wait_appoint_main_type_list {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-bottom: 40rpx;
}
.wait_appoint_main_type_item {
background: #F6F6F6;
border-radius: 28rpx;
padding: 14rpx 26rpx;
font-size: 24rpx;
font-weight: 400;
margin-right: 20rpx;
border: 1rpx solid transparent;
color: #333333;
}
.wait_appoint_main_type_item.active {
background: #FDC500;
border-radius: 28rpx;
}
.wait_appoint_main_type_item.disabled{
}
.wait_appoint_main_type_number {
display: flex;
align-items: center;
margin-bottom: 80rpx;
}
.cart_item_cart_counter {
display: flex;
align-items: center;
}
.cart_item_cart_counter .dec {
width: 34rpx;
height: 34rpx;
background: #FCFCFC;
border-radius: 6rpx;
text-align: center;
line-height: 34rpx;
font-size: 26rpx;
}
.cart_item_cart_counter .graty {
width: 68rpx;
height: 56rpx;
background: #F3F3F3;
border-radius: 6rpx;
text-align: center;
font-size: 24rpx;
font-weight: 400;
color: #333333;
margin: 0 10rpx;
}
.dia_btn_box {
width: 100%;
bottom: 0;
left: 0;
display: flex;
align-items: center;
padding: 24rpx 0;
}
.dia_btn_red {
flex: 1;
text-align: center;
margin: 0 22rpx 0 24rpx;
background: linear-gradient(270deg, #F25314 0%, #E60027 100%);
border-radius: 45rpx;
padding: 10rpx 0;
font-size: 24rpx;
font-weight: 400;
color: #FFFFFF;
}
.dia_btn_red_active {
flex: 1;
text-align: center;
margin: 0 22rpx 0 24rpx;
background: linear-gradient(270deg, #F25314 0%, #E60027 100%);
border-radius: 45rpx;
padding: 20rpx 0;
font-size: 24rpx;
font-weight: 400;
color: #FFFFFF;
}
.dia_btn_yellow {
flex: 1;
margin: 0 24rpx 0 0;
text-align: center;
background: linear-gradient(270deg, #FFAE00 0%, #F6730D 100%);
border-radius: 45rpx;
padding: 10rpx 0;
font-size: 24rpx;
font-weight: 400;
color: #FFFFFF;
}
.dia_btn_yellow_active {
flex: 1;
margin: 0 24rpx 0 0;
text-align: center;
background: linear-gradient(270deg, #FFAE00 0%, #F6730D 100%);
border-radius: 45rpx;
padding: 20rpx 0;
font-size: 24rpx;
font-weight: 400;
color: #FFFFFF;
}
2.4 小程序json
{
"component": true
}
3.效果展示
3.1 vue效果展示
3.2 小程序展示