H5的SKU实例:
前言:上一篇文章说到“管理系统SKU”的相关功能的代码书写;这一篇就讲解一下H5端该怎么用管理系统设置好的sku信息来渲染商品购买时的动态界面。
一、需求
1.首先这个功能其实就是普遍意义上的用户选择好商品后,弹出规格选择框的页面;网上有很多类似SKU相关的插件,用起来也是屡试不爽,但是我这里写出来主要是与上一个管理系统相照应,同时在有些朋友需要个性化的时候可以拿来用,因为一般那种sku插件都是有限制几个规格的,而我这个就比较开放,可以任意添加,同时样式也可以直接进行更改,加以修改就可以是自己的东西;
2.简单说一下这个功能,用户选择购买或者加入购物车后,就会从底部弹起一个弹窗;顶部是商品图片,图片会随规格的不同而改变图片,图片右边是价格和已选择规格,同样也是随规格不同而不同;底部是确认按钮;中间就是我们的规格,每一种规格单独占一块区域,由规格名为标题,规格值循环出的“按钮”为内容;
3.当选择任意一个规格中的规格值时,其他的规格要根据选择来判断自身规格值哪些能够用,哪些必须禁用不可选择;
4.限制用户不可点击规格值的条件有:库存不足、没有该组合(也就是价格为0或者空)
二、成品展示
全部置灰状态(在没有确定规格之前,图片有一个默认值)
三、实现过程梳理
1.html部分,也是vue组件中的一部分,可以先复制运行一下看看效果在用,随便写的案例,命名也不是特别规范用的时候自己规范一下就好了,功能是有的。
<template>
<div class="choose">
<div class="mengo"></div>
<div class="chooseBox">
<div class="top">
<img :src="submitsData.cover||defaultImg" alt="商品图片" />
<div class="content">
<div class="specification">
<div class="money">¥{{submitsData.money}}</div>
<div v-if="submitsData.money==0" class="choosed">选择规格</div>
<div v-else class="choosed">{{JSON.stringify(submitsData.specification)}}</div>
</div>
<div class="num">
<button class="sub btn" @click="sub()"></button>
<input type="number" v-model="submitsData.num" readonly="readonly" />
<button class="add btn" @click="add()"></button>
</div>
</div>
</div>
<div class="specification">
<div v-for="(i,index) in specificationList" :key="index">
<p class="title">{{i.name}}</p>
<div class="content">
<label class="item" :class="{active : j.value==submitsData.specification[index]}" v-for="(j,index2) in i.value" :key="index2">
<input
:id="'r'+index+index2"
type="radio"
@click="typeChekced(index,j.value,0)"
@change="typeChekced(index,j.value,1)"
name="index"
v-model="submitsData.specification[index]"
:value="j.value"
class="lable_input"
:disabled="j.disabled"
/>
<span :class="j.disabled?'greyFont':''">{{j.value}}</span>
</label>
</div>
</div>
</div>
<div class="btn">
<button @click="submits">确定</button>
</div>
</div>
</div>
</template>
2.js部分
(1)这个地方我们就慢慢讲一下,因为比较复杂;首先submitsData这个对象是你要提交给后台的,也就是用户所选规格及其他参数,这里其他参数就不要了就只要specification,也就是我们的规格;讲解代码我都放到代码注释去,细心看就行
export default {
data() {
return{
submitsData:{
specification: [],//当前已选择的规格,也就是提交的数据
num: 1,//数量
speId: "",//规格对应的Id
maxNum: 0,//库存值,也就是最大可选数量
money: 0,//该规格下的价格
cover: "",//该规格的图片
},
specificationList: [],//这是我们这个商品所有的规格组合,也就是上面图中必须全部显示出来的那些按钮
speList: [{speList:["700g","坚果"]},{speList:["500g","坚果"]},{speList:["700g","零食"]}],//后台告诉我们可用的规格
defaultImg: "http://wjjknet.oss-cn-shenzhen.aliyuncs.com/upload/productImages/2020/08/17/32de767b-bf32-4d4c-8897-a34bc7854fab.jpg",//默认商品图片
}
},
mounted() {
this.init();
},
methods:{
init(){
//规格重构,为每一个规格增加一个disable属性,保证后面禁用功能的实现
for (let i = 0; i < this.specificationList.length; i++) {
for (let j = 0; j < this.specificationList[i].value.length; j++) {
this.specificationList[i].value[j] = { 'value': this.specificationList[i].value[j], 'disabled': true }
}
}
//取当前规格对应可选择规格数大小的数组,
//然后再清空数组每一项即可得到一个未选中任何规格的数组
//这句话不怎么好理解,简单的说就是让提交规格中的数组长度等于规格种类数,
//这样用户在选择对应规格的时候数据才会双向绑定,
//如果有3中规格,你数组长度只有2,那么当用户选择第三个规格值的时候就没有绑定值,
//有时候就会出现点不动,或者点了有效果但是没数据;注意使用深拷贝不然会一起动
this.submitsData.specification = JSON.parse(JSON.stringify(this.speList[0].speList));
for (let i = 0; i < this.specificationList.length; i++) {
this.submitsData.specification[i] = "";
}
this.renderBtn();
},
}
}
(2)上面讲了数据准备,接下来将操作相关的JS,下面的都是方法,直接放到上面的methods里面即可
先看第一个方法,也就是我们在init()中首先调用的方法,直接看代码,有详细讲解和数据举例
//通过计算标识出哪些规格的属性是禁用的
renderBtn() {
var list = this.specificationList;
var ownList = this.speList;
var nowList = this.submitsData.specification;
try {
console.log('所有规格:', list)//[[{disabled: false,value: "300g"},{disabled: false,value: "700g"}],[{disabled: false,value: "坚果"},{disabled: false,value: "坚1果"}],]
console.log('可用规格:', ownList)//[{speList:["700g","坚果"]},{speList:["700g","坚果"]},{speList:["700g","坚果"]}]
console.log('选择的规格:', nowList)//["700g","坚果"],这里数组下标的值就是list的第一层数组所在的下标
for (let i = 0; i < list.length; i++) {//必须重置所有状态未禁用,否则会不能正常出现效果
for (let j = 0; j < list[i].value.length; j++) {
list[i].value[j].disabled = true;
}
}
if (list.length == 1) {
for (let i = 0; i < ownList.length; i++) {
for (let j = 0; j < list[0].value.length; j++) {
if (ownList[i].speList[0] == list[0].value[j].value) {
list[0].value[j].disabled = false;
}
}
}
return
}
for (let i = 0; i < nowList.length; i++) {
// console.log('模拟当前在循环被点击按钮为:', (nowList[i] || '未选择'), i)
for (let m = 0; m < list.length; m++) {//m代表规格种类的下标,如果为1就是代表正在处理第一种规格,如重量【300kg,500kg】这个数组
if (m != i) {//去掉非当前循环选择作为参照规格的下标,也就是去掉的规格就直接填充进入对比序列,如去掉300g 那么如果是两个规格的就是["300g","待定"]
for (let j = 0; j < list[m].value.length; j++) {
// console.log('当前处理的按钮为:', list[m].value[j].value)
for (let k = 0; k < ownList.length; k++) {
if (nowList[i] == "") {
if (list[m].value[j].value == ownList[k].speList[m]) {
list[m].value[j].disabled = false;
}
} else {
if (nowList[i] == ownList[k].speList[i] && list[m].value[j].value == ownList[k].speList[m]) {
list[m].value[j].disabled = false;
}
}
// console.log((nowList[i] || '未选择') + '**' + list[m].value[j].value, '处理结果:', list[m].value[j].disabled, i + '_' + j + '_' + k, ownList[k].speList[i], ownList[k].speList[m])
}
}
}
}
}
} catch (e) {
console.log(e)
}
},
(3)用户点击按钮后执行的方法
typeChekced(index, value, int) {
if (int == 0 && this.submitsData.specification[index] != "" && this.submitsData.specification[index] == value) {//这里一定要注意只有当不触发change事件的时候(int值来判断)且需要清除选中的时候去变动我们v-model绑定的值,否则会导致渲染极其卡顿(猜测是多次重绘的结果)
this.submitsData.specification[index] = "";
this.submitsData.speId = null;
this.submitsData.maxNum = 0;
this.submitsData.num = 0;
this.submitsData.money = 0;
this.submitsData.cover = this.defaultImg;
}
if (int == 1) {
let subSpecification = JSON.stringify(this.submitsData.specification);
for (let i of this.speList) {
if (i.specification == subSpecification) {
//下面的赋值因人而异,根据后端反的字段来,这里就是找到对应规格下的相关参数并赋值
this.submitsData.speId = i.id;
this.submitsData.maxNum = i.inventory;
this.submitsData.num = 1;
this.submitsData.money = i.money;
this.submitsData.cover = i.exhibitionImg;
}
}
}
//最关键的就是每次赋完值又要重新计算哪些可选哪些不可选
this.renderBtn();
},
sub() { //数量减
if (this.submitsData.num <= 1) {
return
}
this.submitsData.num--;
},
add() { //数量加
if (this.submitsData.num == 0) {
$.alert("请先选择规格", "提醒");
return;
}
this.submitsData.num++;
return;
},
(4)样式我还是给出来,可以自己进行更改哈(注意这里是相对于窗口定位的哦)
.choose {
background-color: rgba(0, 0, 0, .6);
opacity: 1;
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
z-index: 100;
.mengo {
background-color: rgba(0, 0, 0, .6);
opacity: 1;
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
z-index: 101;
}
.chooseBox {
position: absolute;
height: 80%;
padding: 0.35rem;
width: calc(100% - 0.7rem);
background-color: white;
top: 20%;
left: 0;
right: 0;
z-index: 102;
overflow-y: scroll;
overflow-x: hidden;
display: flex;
flex-direction: column;
.top {
height: 2.19rem;
width: 100%;
display: flex;
flex-direction: row;
img {
width: 2.19rem;
height: 2.19rem;
border-radius: 0.1rem;
object-fit: scale-down;
}
.content {
display: flex;
flex-direction: column;
height: 2.19rem;
margin-left: 0.35rem;
width: calc(100% - 0.35rem - 2.19rem);
.specification {
display: flex;
flex-direction: column;
width: 100%;
height: 1rem;
.money {
font-size: 0.32rem;
color: #ed6969;
height: 0.32rem;
line-height: 0.32rem;
width: 100%;
margin-top: 0.2rem;
}
.choosed {
font-size: 0.28rem;
color: #989898;
margin-top: 0.2rem;
height: 0.28rem;
line-height: 0.28rem;
width: 100%;
}
}
.num {
display: flex;
flex-direction: row;
margin-top: 0.5rem;
justify-content: flex-end;
width: 100%;
height: 0.35rem;
input {
width: 3em;
text-align: center;
font-size: 0.25rem;
color: #989898;
}
.btn {
background: none;
width: 0.33rem;
height: 0.33rem;
background-size: 100%;
background-repeat: no-repeat;
}
.sub {
background-image: url("/static/wx/images/sub.png");
}
.add {
background-image: url("/static/wx/images/add.png");
}
}
}
}
.specification {
margin-top: 0.6rem;
width: 100%;
.title {
font-size: 0.28rem;
color: #333333;
font-weight: bold;
}
.content {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
padding: 0.3rem 0;
margin-bottom: 0.3rem;
.active {
color: #fa694c !important;
background-color: #ffdfda !important;
border: 1px solid #fa694c !important;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.item {
border-radius: 0.1rem;
background-color: #f3f3f3;
font-size: 0.23rem;
color: #333333;
padding: 0.1rem 0.3rem;
display: flex;
align-items: center;
margin-bottom: 0.2rem;
margin-right: 0.2rem;
.lable_input {
width: 0;
height: 0;
}
.img-label {
display: inline-block;
width: 0.5rem;
height: 0.5rem;
background-color: #d5d5d5;
border-radius: 0.1rem;
overflow: hidden;
margin-right: 0.17rem;
}
.greyFont {
color: #cccccc;
}
}
}
}
.btn {
text-align: center;
box-sizing: border-box;
button {
width: 100%;
height: 0.85rem;
border-radius: 50px;
background-color: #fa694c;
color: #fff;
font-size: 0.35rem;
margin-top: 2rem;
}
}
}
}
结语:以上就是手写sku功能的代码,有些地方确实很不容易理解;但是你先测试一下就知道是不是对的,然后再去一步一步的走debug就能懂了。