前端商城商品sku设计和实现

后台模型


需求与实现(模拟实现)

sku
tip:在后台中添加的规格参数,最终会在请求接口后返回规格参数,本次实例有3个规格,每个规格分别有3个选项,其中有一个sku组合是默认项,可随意在后台设置,还有就是禁用项,禁用项主要的目的就是当前规格可能紧急没有库存或其他原因下架的商品,用户是无法选择禁用的sku组合的。

后台返回的规格列表参数数据(模拟数据):

//attributeList 为所有规格list   skuName为规格名,skuValue为规格值
//defaultSpec:默认选中的规格
data:{
    attributeList: '[{"skuName":"颜色","skuValue":["红色","白色","绿色"]},{"skuName":"大小","skuValue":["大","中","小"]},{"skuName":"规格","skuValue":["5L","10L","15L"]}]',
    defaultSpec: '{"颜色":"红色","大小":"大","规格":"5L"}',
}

每次点击规格项时调用接口,返回禁用的sku组合(模拟数据):

//禁用组合字段反参为数组,每一项是json格式的键值对
data:{
  cdMallSkusList: ['{"颜色":"红色","大小":"小","规格":"5L"}', '{"颜色":"白色","大小":"中","规格":"10L"}', '{"颜色":"绿色","大小":"中","规格":"5L"}', '{"颜色":"绿色","大小":"小","规格":"10L"}']
}

基本实现:

sku
html:
<view class="sku">
//外层遍历规格名
        <view class="content1"  v-for="(item, index) in skuList" :key="item[id]">
            <text class="t1">{{ item.name }}</text>
            <view class="list flex-wrap">
              //内层遍历规格项名
                <text
                    v-for="(items, indexs) in item.list"
                    :key="indexs"
                    @tap="formatXY(index,indexs) ? '' : changeTab(index, indexs)"
                    :class="{ selectColor: indexs == item.sidx, disColor: formatXY(index,indexs) }"
                    >
                    {{ items }}
                </text>
            </view>
        </view>
</view>
js(核心):
data(){
  return {
       skuList:[],     //sku列表
       disSkus:[],    //禁用的sku组合
       disBtn:[]       //禁用组合的下标集合
    }
},

//当前使用的是uniapp的生命周期,vue原生项目可以做适当修改。逻辑都是一样的
onLoad(){
      this.getMallDetail()
},

//init初始数据
getMallDetail(){
        let selectSkus = [];               //得到默认选中项,并处理成 ['红色', '大', '5L']格式
        for (let key in JSON.parse(res.data.defaultSpec)) {
            selectSkus.push(JSON.parse(res.data.defaultSpec)[key]);
        }
        //res.data.data.attributeList为调用后端接口返回的字段,字段内容见上方模拟数据
         this.skuList = JSON.parse(res.data.attributeList).map((u, index) => {
         let sidx = u.skuValue.findIndex(item => item == selectSkus[index])   //计算出默认规格的下标,做选中效果使用
        //处理成对象型数组格式,更好处理和数据渲染
                return {
                    name: u.skuName,    //规格名
                    id: index,          //每一项的key
                    list: u.skuValue,   //数组,规格项的名称的集合
                    sidx:sidx           //主要为每一个规格做一个标记,为实现后面的点击变色并且不互相冲突的记录值
                };
        });
        this.getSpecList(selectSkus)             //初始执行一次处理禁用关系函数
 },

//点击切换选项方法:
changeTab(index, indexs) {
            this.skuList[index].sidx = indexs;
        //得到点击选择的规格组合
            const selectInfo = this.skuList.reduce((prev, cur) => {
                if (prev) {
                    return prev + ',' + cur['list'][cur.sidx];
                } else {
                    return cur['list'][cur.sidx];
                }
            }, '');
        this.getSpecList(selectInfo.split(','));
},

// 获取禁用的sku组合和点击后得到的选中的组合
getSpecList(selectSkus) {
          //调用接口获取禁用的sku组合
            this.api.getSpecList().then(res => {
                    this.disSkus = res.data.data.cdMallSkusList.map(a => {
                        let dis = [];
                        for (let key in JSON.parse(a)) {
                            dis.push(JSON.parse(a)[key]);
                        }
                        return dis;
                    });
              //调用下方格式化规格方法
                this.formatSkuValue(selectSkus);
            });
},

// 格式化规格,找出禁选坐标
formatSkuValue(selectSkus) {
            this.disBtn = [];
            let noSelectSkusAll = []
            let skuGroupAll = []

            // selectSkus 默认选中的规格组合
            // skuList 每个规格的各项,所有的规格
            // noSelectSkus 每个规格没有被选中的各项(二维数组,每一个数组对应每一个规格)
            let noSelectSkus = this.skuList.map((a, index) => {
                return a.list
                    .map(b => {
                        if (b != selectSkus[index]) {
                            return b;
                        }
                    })
                    .filter(Boolean);
            });
            
            // noSelectSkusAll 二维数组,过滤出排除当前规格项的其他已选中的规格项.例如:
            // ['红','大','5L']为选中项,那么过滤后noSelectSkusAll的值就是:[['大','5L'],['红','5L'],['红','大']]
            
            for(let i = 0;i<selectSkus.length;i++){
                let arr = selectSkus.filter(item => {
                    return item != selectSkus[i];
                });
                noSelectSkusAll.push(arr)
            }
            
            // skuGroupAll 三维数组或二维数组,取决于规格有多少,大于2个规格时就是三维数组,
            // 组合出每一个规格选中的项和上述 noSelectSkusAll得到的值进行组合.例如:
            // [['绿','紫'],['中','小'],['10L','15L']]是noSelectSkusAll的值,['红','大','5L']是选中的组合项,那么组合就是[[['绿','大','5L'],['紫','大','5L']],[['红','小','5L'],['红','中','5L']],[['红','大','10L'],['红','大','15L']]]
            
            for(let i = 0;i<noSelectSkus.length;i++){
                let arr = []
                noSelectSkus[i].forEach(item => {
                    noSelectSkusAll[i].splice(i, 0, item);
                    arr.push(JSON.parse(JSON.stringify(noSelectSkusAll[i])));
                    noSelectSkusAll[i].splice(i, 1);
                });
                skuGroupAll.push(arr)
            }
            
            //disSkus 是提前设定好的禁选的sku组合
            //把skuGroupAll中的组合和禁选的组合进行比对,完全相同的就是需要禁选的,
            //通过遍历skuList所有的规格的每一项,比如第0下标的规格和h[0]比对,一一对比,等到相同项就是最终的禁选项,如果相同就可以得出0下标的第几项了,比如第2项是禁选的那么坐标就是[0,1]
            //最终把有禁用项的下标存储到数组中,有多少个规格就存多少长度的数组,就可以准确的知道每一个规格哪一项是禁选的准确坐标
            //例如:[[1],[0,1],[2]]为最终得到的禁选的下标,那么第1个规格的第2个禁选,第3个规格的第3个禁选,....
            for(let j = 0;j<skuGroupAll.length;j++){
                let arr = []
                skuGroupAll[j].map((v, i) => {
                    this.disSkus.map((h, s) => {
                        if (v.toString() == h.toString()) {
                            console.log('禁用');
                            this.skuList[j].list.map((a, index) => {
                                if (a == h[j]) {
                                    arr.push(index);
                                }
                            });
                        }
                    });
                });
                this.disBtn.push(arr)
            }
        console.log('disBtn',this.disBtn);
},

// index:每个规格的下标,相当于y轴
// indexs:每个规格里每项的下标,相当于x轴
// 获取禁用项具体坐标
formatXY(index,indexs){
        let disStr = ''
        this.disBtn.map((a, index1) => {
            // disBtn:保存的是最终需要被禁选的下标,二维数组组成,每个数组的小标为y轴即规格,数组中的数字就是x轴即规格的每项,例如:[[2],[0]] 就是(0,2)和(1,0)被禁选
            // 当y轴的规格名和x轴的规格项名都相等时,就找到了需要禁选的那一项
                a.map(b => {
                    disStr = (index == index1 && indexs == b) || disStr
                })
            });
        console.log('disStr',disStr);
        // disStr返回true则当前项是禁选的
        return disStr
},
css(scss):
//创建一个scss 的minix用作公用
@mixin str($size,$weight,$color) {
  font-size: $size;
  font-family: PingFang SC;
  font-weight: $weight;
  color:$color;
  opacity: 1;
}

.sku {
    .content1,
    .content2 {
      .t1 {
          @include str(28px, bold, #333);
      }

      .flex_warp {
          display: flex;
          flex-wrap: wrap;
      }

      .list {
          display: flex;
          flex-wrap: wrap;

        p {
          display: inline-block;
          width: 218px;
          height: 60px;
          border-radius: 8px;
          background: rgba(237, 237, 237, 1);
          text-align: center;
          line-height: 60px;
          cursor: pointer;
          @include str(28px, 400, #444);
        }

        p:nth-child(n + 4) {
          margin-top: 20px;
        }

        p:nth-child(n + 2) {
          margin-left: 10px;
        }
      }
    }
    //选择时的按钮颜色变化
    .selectColor {
      background: #fc6d18 !important;
      color: #fff !important;
    }

    //禁用项的样式
    .disColor {
      text-decoration: line-through !important;
      background: rgba(237, 237, 237, 1) !important;
      color: #ccc !important;
      opacity: 0.1;
    }
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 文章目录一、编程规约(一) 命名风格(二) 常量定义(三) 代码格式(四) OOP 规约(五) 日期时间(六) 集...
    wqjcarnation阅读 183评论 0 0
  • 经销商走向强则更强,弱则更弱的二级化! 疫情后,经销商在重新思考企业未来发展的定位,小编也和全国50多个城市的...
    d57c6d75c125阅读 71评论 0 1
  • 目标:选择的图片文件,要给到 img 标签上做纯前端的预览 img 标签的 src 值 * 只能是图片的 “链接地...
    小丸子_7043阅读 274评论 0 0
  • 头条 Roblox 正在整合生成式人工智能[https://archive.ph/pCSBd] 热门在线游戏 Ro...
    数科每日阅读 215评论 0 1
  • python找出某个文件夹下某个后缀的文件 该函数接受一个文件夹路径和一个后缀名作为参数,并返回一个包含所有以该后...
    人工zz研究员阅读 53评论 0 1