前端商城商品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;
    }
  }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 文章目錄一、編程規約(一) 命名風格(二) 常量定義(三) 代碼格式(四) OOP 規約(五) 日期時間(六) 集...
    wqjcarnation閱讀 199評論 0 0
  • 經銷商走向強則更強,弱則更弱的二級化! 疫情后,經銷商在重新思考企業未來發展的定位,小編也和全國50多個城市的...
    d57c6d75c125閱讀 77評論 0 1
  • 目標:選擇的圖片文件,要給到 img 標簽上做純前端的預覽 img 標簽的 src 值 * 只能是圖片的 “鏈接地...
    小丸子_7043閱讀 309評論 0 0
  • 頭條 Roblox 正在整合生成式人工智能[https://archive.ph/pCSBd] 熱門在線游戲 Ro...
    數科每日閱讀 233評論 0 1
  • python找出某個文件夾下某個后綴的文件 該函數接受一個文件夾路徑和一個后綴名作為參數,并返回一個包含所有以該后...
    人工zz研究員閱讀 58評論 0 1