Sku如何进行设计
欢迎各位小伙伴来观看这篇文章!最近做项目需要对sku进行设计并实现,以下是实现的效果。
接下来我们开始吧!!!
数据库的设计
数据库中我们总共有两个表,一个商品表,一个sku表。sql语句
商品表
CREATE TABLE `goods` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`brand` varchar(50) DEFAULT NULL,
`thumb` varchar(100) DEFAULT NULL,
`category_id` int(8) DEFAULT NULL,
`state` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
sku表
CREATE TABLE `sku` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`price` int(10) DEFAULT NULL,
`stock` int(10) DEFAULT NULL,
`marketPrice` int(10) DEFAULT NULL,
`costPrice` int(10) DEFAULT NULL,
`specs` varchar(100) DEFAULT NULL,
`goods_id` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
里面的一些数据
商品表的信息
sku表的信息
sku的specs字段我的是varchar类型因为我的mysql是5.6版本的,5.7版本及以上可以使用json类型。此处我是按照我的实际情况走的,向sku插入数据时
//item是服务器得到前端传过来的数据
'insert into sku (price,stock,marketPrice,costPrice,specs,goods_id)
values (?,?,?,?,?,?)',[item.price, item.stock, item.sPrice, item.cPrice, JSON.stringify(item.specs), result.insertId]
以上是对数据库设计的解释,以防小伙伴接下来对拿到的数据不明白。
以上就涉及了两个表,但是我的项目涉及到了其他的数据所以这是按照我的项目拿的数据,但是这个sku的设计就涉及到了上边的两个表
从后端拿数据
接下来是后端给前端的数据。
//利用node搭建的服务器
//在这里后端拿到前端发过来的数据商品id,有关商品某个sku的id.
async function selectGoodsSpecs(ctx) {
let id = ctx.query.id
let sku_id = ctx.query.sku_id
if (!id || !sku_id) {
return ctx.body = { code: 0, msg: '该商品不存在', data: null }
}
// 此处应该加上是否已将上架,此搜索为某个商品的信息
let [[good]] = await db.query('select * from goods where id=?', [id])
// 搜索该商品的轮播图的照片
let [imgs] = await db.query('select * from goods_imgs where goods_id=?', [id])
let imgsLength = imgs.length
// 搜索已存在的商品规格组合sku
let [skusRaw] = await db.query('select * from sku where goods_id=?', [id])
for (const it of skusRaw) {
it.specs = JSON.parse(it.specs)
}
// 获取当前的商品规格
let [[currentSku]] = await db.query('select * from sku where id=?', [sku_id])
currentSku.specs = JSON.parse(currentSku.specs)
good.imgs = imgs
good.imgsLength = imgsLength
good.skusRaw = skusRaw
good.currentSku = currentSku
ctx.body = { code: 1, msg: '查找成功', data: good }
}
前端拿到的数据是华为电视这个商品的
//id:商品id
//name:商品名字
//brand:品牌
//thumb:封面图
//category_id:分类id
//state:商品状态
//imgs:商品轮播图
//imgsLength:商品轮播图的个数
//skusRaw:该商品目前拥有的规格sku
//currentSku:默认的商品sku
{
"id": 35,
"name": "华为智慧屏 SE 65英寸 畅连通话版 超薄电视 4K超高清智能液晶电视机 HD65DESY 2+16GB 【搭载HarmonyOS 2】 ",
"brand": "华为",
"thumb": "2021/11/10/1639137174811华为1.dpg",
"category_id": 38,
"state": 1,
"imgs": [
{
"id": 8,
"url": "2021/11/10/1639137289124华为1-1.dpg.webp",
"goods_id": 35
},
{
"id": 9,
"url": "2021/11/10/1639137294323华为1-2.dpg.webp",
"goods_id": 35
},
{
"id": 10,
"url": "2021/11/10/1639137299589华为1-3.dpg.webp",
"goods_id": 35
},
{
"id": 11,
"url": "2021/11/10/1639137306225华为1-4.dpg.webp",
"goods_id": 35
},
{
"id": 12,
"url": "2021/11/10/1639137312438华为1-5.dpg.webp",
"goods_id": 35
}
],
"imgsLength": 5,
"skusRaw": [
{
"id": 27,
"price": 329900,
"stock": 111,
"marketPrice": 299900,
"costPrice": 200000,
"specs": {
"颜色": "红色",
"运行内存": "4G",
"存储": "64G"
},
"goods_id": 35
},
{
"id": 28,
"price": 259900,
"stock": 222,
"marketPrice": 199900,
"costPrice": 149900,
"specs": {
"颜色": "金色",
"运行内存": "6G",
"存储": "128G"
},
"goods_id": 35
},
{
"id": 29,
"price": 269900,
"stock": 333,
"marketPrice": 189900,
"costPrice": 129900,
"specs": {
"颜色": "褐色",
"运行内存": "4G",
"存储": "256G"
},
"goods_id": 35
},
{
"id": 30,
"price": 289500,
"stock": 4444,
"marketPrice": 179900,
"costPrice": 149900,
"specs": {
"颜色": "黑色",
"运行内存": "4G",
"存储": "128G"
},
"goods_id": 35
},
{
"id": 31,
"price": 312600,
"stock": 555,
"marketPrice": 159900,
"costPrice": 148500,
"specs": {
"颜色": "白色",
"运行内存": "4G",
"存储": "64G"
},
"goods_id": 35
},
{
"id": 32,
"price": 245600,
"stock": 666,
"marketPrice": 195600,
"costPrice": 111100,
"specs": {
"颜色": "红色",
"运行内存": "6G",
"存储": "128G"
},
"goods_id": 35
},
{
"id": 33,
"price": 327800,
"stock": 777,
"marketPrice": 169800,
"costPrice": 145800,
"specs": {
"颜色": "金色",
"运行内存": "8G",
"存储": "64G"
},
"goods_id": 35
},
{
"id": 34,
"price": 369900,
"stock": 888,
"marketPrice": 219900,
"costPrice": 129900,
"specs": {
"颜色": "褐色",
"运行内存": "8G",
"存储": "256G"
},
"goods_id": 35
},
{
"id": 35,
"price": 378400,
"stock": 999,
"marketPrice": 184500,
"costPrice": 112200,
"specs": {
"颜色": "白色",
"运行内存": "8G",
"存储": "256G"
},
"goods_id": 35
},
{
"id": 36,
"price": 358900,
"stock": 1010,
"marketPrice": 190000,
"costPrice": 150000,
"specs": {
"颜色": "黑色",
"运行内存": "4G",
"存储": "64G"
},
"goods_id": 35
}
],
"currentSku": {
"id": 27,
"price": 329900,
"stock": 111,
"marketPrice": 299900,
"costPrice": 200000,
"specs": {
"颜色": "红色",
"运行内存": "4G",
"存储": "64G"
},
"goods_id": 35
}
}
我们将得到的这个对象赋值给goodsInfo.data
let route = useRoute();
let goodsInfo = reactive({
data: "",
});
// 生命周期函数获取数据库数据
onBeforeMount(async function () {
let id = route.query.id;
let sku_id = route.query.sku_id;
let res = await selectGoodsSpecs({ id, sku_id });
//console.log(res.data)
goodsInfo.data = res.data;
// 默认当前选中的值
changeSkuGoodsSpecs(goodsInfo.data.currentSku.specs);
});
changeSkuGoodsSpecs函数
//通过匹配到的值改变goodsInfo.skuStatus的状态值
function changeSkuGoodsSpecs(defaultSkuSpecsPart) {
let skusRaw = goodsInfo.data.skusRaw;
let skuStatus = {};
for (const it of skusRaw) {
let specs = it.specs;
for (const key in specs) {
if (!skuStatus[key]) {
skuStatus[key] = {};
}
skuStatus[key][specs[key]] = { clicked: false, disabled: true };
}
}
// 得到skuStatus,规格的具体信息,并且渲染到了界面上
goodsInfo.data.skuStatus = skuStatus;
// 通过默认值修改goodsInfo.skuStatus,为点击的状态
for (const key in defaultSkuSpecsPart) {
goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
}
// 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
//对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
let selectSkuSpecsPartArr = [];
selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
toStrSkuSpecsChild(selectSkuSpecsPartArr);
let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
console.log(selectSkuSpecsPartArrToOne)
// 需要得到已经存在的skusRaw组合的specs值的子组合
let skuSpecsChild = [];
for (const it in skusRaw) {
let specs = skusRaw[it].specs;
skuSpecsChild.push(powerset(Object.values(specs)));
}
// 已经得到skuRaw每条信息的specs的子组合
// 对skuSpecsChild进行进一步处理
toStrSkuSpecsChild(skuSpecsChild);
console.log(skuSpecsChild)
// 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
// 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
let selectAboultIndex = [];
for (const i in skuSpecsChild) {
for (const item of selectSkuSpecsPartArrToOne) {
if (selectSkuSpecsPartArrToOne.length > 1) {
if (
item.includes("-") &&
skuSpecsChild[i].includes(item) &&
!selectAboultIndex.includes(i)
) {
selectAboultIndex.push(i);
}
} else {
if (skuSpecsChild[i].includes(item)) {
selectAboultIndex.push(i);
}
}
}
}
//selectAboultIndex得到了匹配结果
// 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
// 修改goodsInfo.skuStatus里的值对应的状态
for (const value of selectAboultIndex) {
for (const key in skusRaw[value].specs) {
// key键值
// console.log(key)
// key键值对应的值
// 变成可点状态
goodsInfo.data.skuStatus[key][
skusRaw[value].specs[key]
].disabled = false;
}
}
}
首先先渲染上去
首先考虑的问题是将得到的商品的所有规格渲染上去。从数据中可以看出。该商品有是三种规格。颜色,运行内存,存储。
颜色:[“红色”,“金色”,“褐色”,“黑色”,“白色”]
运行内存:[“4G”,“6G”,“8G”]
存储:[“64G”,“128G”,“256G”]
但是我们还要考虑规格按钮的状态。是否可以点,是否不可以点。是否点击了,所以我们需要得到这样的数据
let skusRaw = goodsInfo.data.skusRaw;
let skuStatus = {};
for (const it of skusRaw) {
let specs = it.specs;
for (const key in specs) {
if (!skuStatus[key]) {
skuStatus[key] = {};
}
skuStatus[key][specs[key]] = { clicked: false, disabled: true };
}
}
// 得到skuStatus,规格的具体信息,并且渲染到了界面上
goodsInfo.skuStatus = skuStatus;
//得到的skuStatus是这样的
{
"颜色": {
"红色": {
"clicked": false,
"disabled": true
},
"金色": {
"clicked": false,
"disabled": true
},
"褐色": {
"clicked": false,
"disabled": true
},
"黑色": {
"clicked": false,
"disabled": true
},
"白色": {
"clicked": false,
"disabled": true
}
},
"运行内存": {
"4G": {
"clicked": false,
"disabled": true
},
"6G": {
"clicked": false,
"disabled": true
},
"8G": {
"clicked": false,
"disabled": true
}
},
"存储": {
"64G": {
"clicked": false,
"disabled": true
},
"128G": {
"clicked": false,
"disabled": true
},
"256G": {
"clicked": false,
"disabled": true
}
}
}
'clicked’为false则没有被点击状态,为true则是被点击状态
'disabled’为false则可以点击,为true则不可以被点击。
通过goodsInfo.skuStatus就可以渲染上去了。
<template>
<div class="specs" v-for="(item, index) in goodsInfo.skuStatus" :key="index">
<div class="title">{{ index }}</div>
<div class="specs-list">
<template v-for="(it, index2) in item" :key="index2">
<div @click="changeSpecs(index, index2)" class="specs-item" :class="it">
{{ index2 }}
</div>
</template>
</div>
</div>
</template>
通过动态绑定:class=‘it’.当it为{
“clicked”: false,
“disabled”: true
}
则class=‘disabled’.剩下的组合我就不用解释了吧!!!!
CSS样式
//这是less的写法
.specs {
.title {
font-size: 0.72rem;
padding: 10px 0;
}
.specs-list {
display: flex;
flex-wrap: wrap;
font-size: 12px;
line-height: 20px;
.specs-item {
padding: 3px 12px;
background-color: #f7f7f7;
color: #333;
margin: 5px 10px 5px 0;
height: 20px;
border-radius: 20px;
border: 1px solid #ddd;
&.disabled {
// background-color: cornflowerblue;
border: 1px dotted #ccc;
background-color: #fefefe;
color: #ccc;
}
&.clicked {
background-color: #f81818;
color: #fff;
border: 1px solid #f81818;
}
}
}
}
思路最重要
目前走到这里应该是已经渲染上去,并且样式都是灰色的
但是接下来该怎么办呢,首先是了解到我们已经拥有了可以控制每个按钮的可不可以被点击,被点击了吗。
第一步:当拿到数据时,先得到所有的规格(上边已经拿到了),通过当前的默认规格去找出和它有关系的规格,例如:
此时默认红色,4G,64G.这个规格与skuRaw的所有规格去匹配。
组合有红色-4G,红色-64G,4G-64G,红色-4G-64G.skuRaw里边的规格凡是匹配到这些组合的都可点,将匹配到的skuRaw下标存到一个数组里。
组合为黑色,4G,64G存在,白色,4G,64G也存在
当点击黑色
此时为黑色,4G,64G.组合有黑色-4G,黑色-64G,4G-64G,黑色-4G-64G。去匹配skuRaw.
黑色-4G,可以匹配到 存储的64G和128G.黑色-64G可以匹配到运行内存的4G.4G-64G可以匹配到红色,白色。所以我们要得到当前组合的子组合。
我们要得到这样的数据
['红色', '4G', '红色-4G', '64G', '红色-64G', '4G-64G', '红色-4G-64G']
1.第一步处理
// 求数组的所有子组合(求幂集)
function powerset(arr) {
var ps = [[]];
for (var i = 0; i < arr.length; i++) {
for (var j = 0, len = ps.length; j < len; j++) {
ps.push(ps[j].concat(arr[i]));
}
}
return ps;
}
2.第二步处理
//对skuSpecsChild进行进一步处理,将数组里边的数组转换成字符串
function toStrSkuSpecsChild(arr) {
for (const i in arr) {
let childArr = arr[i];
for (const j in childArr) {
if (childArr[j] != []) {
childArr[j] = childArr[j].join("-");
}
}
//删除数组的第一个元素,因为它是空的
childArr.shift();
// 原数组被改变childArr已经没有了空值
}
}
3.这是得到当前默认规格的子组合
// 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
//defaultSkuSpecsPart为{"颜色": "红色","运行内存": "4G","存储": "64G" }这样的数据
let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
//对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
let selectSkuSpecsPartArr = [];
selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
toStrSkuSpecsChild(selectSkuSpecsPartArr);
let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
console.log(selectSkuSpecsPartArrToOne)
4.还需得到skuRaw所有规格的子组合
// 需要得到已经存在的skusRaw组合的specs值的子组合
let skuSpecsChild = [];
for (const it in skusRaw) {
let specs = skusRaw[it].specs;
skuSpecsChild.push(powerset(Object.values(specs)));
}
// 已经得到skuRaw每条信息的specs的子组合
// 对skuSpecsChild进行进一步处理
toStrSkuSpecsChild(skuSpecsChild);
当前总共十条数据
[
[
"红色",
"4G",
"红色-4G",
"64G",
"红色-64G",
"4G-64G",
"红色-4G-64G"
],
[
"金色",
"6G",
"金色-6G",
"128G",
"金色-128G",
"6G-128G",
"金色-6G-128G"
],
[
"褐色",
"4G",
"褐色-4G",
"256G",
"褐色-256G",
"4G-256G",
"褐色-4G-256G"
],
[
"黑色",
"4G",
"黑色-4G",
"128G",
"黑色-128G",
"4G-128G",
"黑色-4G-128G"
],
[
"白色",
"4G",
"白色-4G",
"64G",
"白色-64G",
"4G-64G",
"白色-4G-64G"
],
[
"红色",
"6G",
"红色-6G",
"128G",
"红色-128G",
"6G-128G",
"红色-6G-128G"
],
[
"金色",
"8G",
"金色-8G",
"64G",
"金色-64G",
"8G-64G",
"金色-8G-64G"
],
[
"褐色",
"8G",
"褐色-8G",
"256G",
"褐色-256G",
"8G-256G",
"褐色-8G-256G"
],
[
"白色",
"8G",
"白色-8G",
"256G",
"白色-256G",
"8G-256G",
"白色-8G-256G"
],
[
"黑色",
"4G",
"黑色-4G",
"64G",
"黑色-64G",
"4G-64G",
"黑色-4G-64G"
]
]
两者进行匹配
接下来就是最重要且是难点的部分。
如何匹配得到匹配结果
// 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
// 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
let selectAboultIndex = [];
for (const i in skuSpecsChild) {
for (const item of selectSkuSpecsPartArrToOne) {
//当子组合大于1个的时候,还有子组合为1的时候
if (selectSkuSpecsPartArrToOne.length > 1) {
if (
item.includes("-") &&
skuSpecsChild[i].includes(item) &&
!selectAboultIndex.includes(i)
) {
selectAboultIndex.push(i);
}
} else {
if (skuSpecsChild[i].includes(item)) {
selectAboultIndex.push(i);
}
}
}
}
得到一个数组,里边存的是skuRaw的(总共10条)第几条数据的下标。
还有一个难点。如何通过这个数组改变按钮和颜色。那就需要操作skuStatus
1.得到的界面结果哪些可点,哪些不可点
// 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
// 修改goodsInfo.skuStatus里的值对应的状态
for (const value of selectAboultIndex) {
for (const key in skusRaw[value].specs) {
//key键
goodsInfo.data.skuStatus[key][
skusRaw[value].specs[key]
].disabled = false;
}
}
2.得到的结果是哪些被点了,哪些还是正常状态
for (const key in defaultSkuSpecsPart) {
goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
}
处理点击事件
首先有两种结果,当你点正常状态下的时候,其他规格不变,和它同一规格下的,之前的红色变为正常状态,被点击的变成红色。另一种结果当点击的为不能被点击的,被点击变为红色。正常调用changeSkuGoodsSpecs函数
点击事件的代码
// 点击改变事件
function changeSpecs(key, value) {
let selectSkuSpecsPart = {};
// 在此需要判断点击的规格disabled的状态
if (goodsInfo.data.skuStatus[key][value].disabled) {
selectSkuSpecsPart[key] = value;
} else {
// 将这种规格的点击状态变为false
for (const item in goodsInfo.data.skuStatus[key]) {
goodsInfo.data.skuStatus[key][item].clicked = false;
}
// 获取当前所选的
//点击后的状态为红色
goodsInfo.data.skuStatus[key][value].clicked = true;
selectSkuSpecsPart = getSelectSpecs();
}
// 当点击后需要获取当前的specs并调用changeSkuGoodsSpecs
changeSkuGoodsSpecs(selectSkuSpecsPart);
// 计算价格
if (
Object.keys(goodsInfo.data.currentSku.specs).length ==
Object.keys(selectSkuSpecsPart).length
) {
// 更新当前的 sku 信息
for (const it of goodsInfo.data.skusRaw) {
if (objectIsEqual(it.specs, selectSkuSpecsPart)) {
goodsInfo.data.currentSku = it;
}
}
}
selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
ctx.emit("getPrice", price, selectSkuSpecs.value);
}
总结
该设计持续更新中,有不懂小伙伴可以私信或者评论中留言。该页面我封成了一个组件。 以下为该组件全部代码。
<template>
<div class="content">
<van-row>
<van-col span="6">
<img class="img" :src="'http://127.0.0.1:3010/' + thumb" alt="" />
</van-col>
<van-col offset="1" span="17">
<span class="money">¥{{ price }}</span>
<p>
<span>已选:</span>
<span
class="selected"
v-for="(value, index) in selectSkuSpecs"
:key="index"
>{{ value }}
</span>
<span>{{ selectSkuNum }}件</span>
</p>
<p></p>
</van-col>
</van-row>
<div
class="specs"
v-for="(item, index) in goodsInfo.skuStatus"
:key="index"
>
<div class="title">{{ index }}</div>
<div class="specs-list">
<template v-for="(it, index2) in item" :key="index2">
<div
@click="changeSpecs(index, index2)"
class="specs-item"
:class="it"
>
{{ index2 }}
</div>
</template>
</div>
</div>
<!-- 步进器 -->
<div class="num">
<div>数量</div>
<div>
<van-stepper
v-model="selectSkuNum"
theme="round"
button-size="22"
disable-input
@change="changeSkuNum"
/>
</div>
</div>
<div class="btn">
<!-- <van-button round type="warning">查看相似商品</van-button>
<van-button round type="primary">到货通知</van-button> -->
<van-button @click="addShopping" round type="warning"
>加入购物车</van-button
>
<van-button round type="danger">立即购买</van-button>
</div>
</div>
</template>
<script>
import { ref, reactive, watch, toRef } from "vue";
import { useRoute } from "vue-router";
import { selectGoodsSpecs } from "../../api/user/goodsSpecs";
export default {
props: ["close", "thumb"],
setup(props, ctx) {
let route = useRoute();
let goodsInfo = reactive({
data: "",
});
// 定义当前的current
let price = ref("");
// 商品已选的内容字符串显示
let selectSkuSpecs = ref("");
// 当前sku的数量
let selectSkuNum = ref(1);
// 生命周期函数获取数据库数据
(async function () {
let id = route.query.id;
let sku_id = route.query.sku_id;
let res = await selectGoodsSpecs({ id, sku_id });
goodsInfo.data = res.data;
console.log(goodsInfo)
// 默认当前选中的值
price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
ctx.emit("getPrice", price);
selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
changeSkuGoodsSpecs(goodsInfo.data.currentSku.specs);
})();
// 加入购物车的事件
function addShopping() {
ctx.emit("addShopping", goodsInfo.data.currentSku.id, selectSkuNum.value);
}
// 点击改变事件
function changeSpecs(key, value) {
let selectSkuSpecsPart = {};
// 在此需要判断点击的规格disabled的状态
if (goodsInfo.data.skuStatus[key][value].disabled) {
selectSkuSpecsPart[key] = value;
} else {
// 将这种规格的点击状态变为false
for (const item in goodsInfo.data.skuStatus[key]) {
goodsInfo.data.skuStatus[key][item].clicked = false;
}
// 获取当前所选的
//点击后的状态为红色
goodsInfo.data.skuStatus[key][value].clicked = true;
selectSkuSpecsPart = getSelectSpecs();
}
// 当点击后需要获取当前的specs并调用changeSkuGoodsSpecs
changeSkuGoodsSpecs(selectSkuSpecsPart);
// 计算价格
if (
Object.keys(goodsInfo.data.currentSku.specs).length ==
Object.keys(selectSkuSpecsPart).length
) {
// 更新当前的 sku 信息
for (const it of goodsInfo.data.skusRaw) {
if (objectIsEqual(it.specs, selectSkuSpecsPart)) {
goodsInfo.data.currentSku = it;
}
}
}
selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
ctx.emit("getPrice", price, selectSkuSpecs.value);
}
// 每次点击时都要重新获取当前的规格
function getSelectSpecs() {
let selectSkuSpecsPart = {};
for (const it in goodsInfo.data.skuStatus) {
for (const item in goodsInfo.data.skuStatus[it]) {
if (goodsInfo.data.skuStatus[it][item].clicked) {
selectSkuSpecsPart[it] = item;
}
}
}
return selectSkuSpecsPart;
}
//通过匹配到的值改变goodsInfo.skuStatus的状态值
function changeSkuGoodsSpecs(defaultSkuSpecsPart) {
let skusRaw = goodsInfo.data.skusRaw;
let skuStatus = {};
for (const it of skusRaw) {
let specs = it.specs;
for (const key in specs) {
if (!skuStatus[key]) {
skuStatus[key] = {};
}
skuStatus[key][specs[key]] = { clicked: false, disabled: true };
}
}
// 得到skuStatus,规格的具体信息,并且渲染到了界面上
goodsInfo.data.skuStatus = skuStatus;
// 通过默认值修改goodsInfo.skuStatus,为点击的状态
for (const key in defaultSkuSpecsPart) {
goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
}
// 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
//对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
let selectSkuSpecsPartArr = [];
selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
toStrSkuSpecsChild(selectSkuSpecsPartArr);
let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
console.log(selectSkuSpecsPartArrToOne)
// 需要得到已经存在的skusRaw组合的specs值的子组合
let skuSpecsChild = [];
for (const it in skusRaw) {
let specs = skusRaw[it].specs;
skuSpecsChild.push(powerset(Object.values(specs)));
}
// 已经得到skuRaw每条信息的specs的子组合
// 对skuSpecsChild进行进一步处理
toStrSkuSpecsChild(skuSpecsChild);
console.log(skuSpecsChild)
// 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
// 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
let selectAboultIndex = [];
for (const i in skuSpecsChild) {
for (const item of selectSkuSpecsPartArrToOne) {
if (selectSkuSpecsPartArrToOne.length > 1) {
if (
item.includes("-") &&
skuSpecsChild[i].includes(item) &&
!selectAboultIndex.includes(i)
) {
selectAboultIndex.push(i);
}
} else {
if (skuSpecsChild[i].includes(item)) {
selectAboultIndex.push(i);
}
}
}
}
//selectAboultIndex得到了匹配结果
// 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
// 修改goodsInfo.skuStatus里的值对应的状态
for (const value of selectAboultIndex) {
for (const key in skusRaw[value].specs) {
// key键值
// console.log(key)
// key键值对应的值
// 变成可点状态
goodsInfo.data.skuStatus[key][
skusRaw[value].specs[key]
].disabled = false;
}
}
}
// 求数组的所有子组合(求幂集)
function powerset(arr) {
var ps = [[]];
for (var i = 0; i < arr.length; i++) {
for (var j = 0, len = ps.length; j < len; j++) {
ps.push(ps[j].concat(arr[i]));
}
}
return ps;
}
//对skuSpecsChild进行进一步处理,将数组里边的数组转换成字符串
function toStrSkuSpecsChild(arr) {
for (const i in arr) {
let childArr = arr[i];
for (const j in childArr) {
if (childArr[j] != []) {
childArr[j] = childArr[j].join("-");
}
}
//删除数组的第一个元素,因为他是空的
childArr.shift();
// 原数组被改变childArr已经没有了空值
}
}
// 判断两个对象的键值是否完全相等(浅比对)
function objectIsEqual(obj1, obj2) {
for (const key in obj1) {
if (obj1[key] != obj2[key]) {
return false;
}
}
for (const key in obj2) {
if (obj1[key] != obj2[key]) {
return false;
}
}
return true;
}
watch(
function () {
return props.close;
},
function (a) {
if (a) {
changeSkuGoodsSpecs(selectSkuSpecs.value);
ctx.emit("getSpecs", selectSkuSpecs);
}
}
);
// 修改已选的sku数量
function changeSkuNum() {
ctx.emit("getNum", selectSkuNum.value);
}
return {
goodsInfo: toRef(goodsInfo, "data"),
price,
changeSpecs,
selectSkuNum,
changeSkuNum,
selectSkuSpecs,
addShopping,
};
},
};
</script>
<style lang='less' scoped>
.img {
width: 100%;
}
.custom-indicator {
position: absolute;
right: 5px;
bottom: 5px;
padding: 2px 5px;
font-size: 12px;
background: rgba(0, 0, 0, 0.1);
}
// 点击购买的动作面板的样式
.content {
padding: 35px 16px 10px;
.img {
width: 100%;
}
/deep/ .van-col--17 {
padding-top: 10px;
.money {
font-size: 25px;
color: red;
}
p {
padding-top: 12px;
margin: 0px !important;
font-size: 13px;
.selected {
margin-right: 15px;
}
}
}
}
.specs {
.title {
font-size: 0.72rem;
padding: 10px 0;
}
.specs-list {
display: flex;
flex-wrap: wrap;
font-size: 12px;
line-height: 20px;
.specs-item {
padding: 3px 12px;
background-color: #f7f7f7;
color: #333;
margin: 5px 10px 5px 0;
height: 20px;
border-radius: 20px;
border: 1px solid #ddd;
&.disabled {
// background-color: cornflowerblue;
border: 1px dotted #ccc;
background-color: #fefefe;
color: #ccc;
}
&.clicked {
background-color: #f81818;
color: #fff;
border: 1px solid #f81818;
}
}
}
}
.num {
padding: 20px 0;
display: flex;
justify-content: space-between;
font-size: 0.72rem;
}
.btn {
display: flex;
justify-content: space-between;
.van-button {
width: 48%;
height: 36px;
}
}
</style>