<template> <div class="wrap-sku"> <div class="product-box"> <div class="product-content"> <div class="product-delcom" v-for="(productItem,indexs) in list" :key="indexs"> <p>{{ productItem.name }}</p> <ul class="product-footerlist"> <!-- currentChoose[ProductItem.name] === oItem.name // 选中项对象中Key是否存在规格属性 存在就选上red 否则oItem.hasStock代表是否可以被选择,不能被选择就直接置灰色 --> <li v-for="(oItem,index) in productItem.items" :key="index" @click="clickSku(productItem.name, oItem)" :class="[currentChoose[productItem.name] === oItem.name ? 'activeStock' : oItem.hasStock ? 'isStock' : 'noStock']"> {{ oItem.name }} </li> </ul> </div> </div> <div style="font-size: 20px;margin-top: 100px">选中的属性: 性别:{{ currentChoose.sex }}, 颜色:{{ currentChoose.color }}, 大小:{{ currentChoose.size }}</div> </div> </div> </template> <script> // 需要被找到的规格值数组 (创造子集数组是非常重要的一步事情) const getKey = (currentChoose, groupName, item) => { const obj = { ...currentChoose } // 这一步也是非常重要的 // 很多人不懂的事,我点击男 groupName是sex 然后 item.name值是男 // obj[groupName]是对象里面的sex值等于 刚刚传进来的 (item.name值是男) // 那么obj打出来为啥还有sex值为女的呢。这里按道理都是男啊,为啥呢 // 理由如下: 当我点击规格事件的 currentChoose[对应的key值已经被我存起来了](具体请看点击事件) obj[groupName] = item.name // 建造子集数组(重要一步)它将是拿去去交集的一个点 const arr = [] // 找到存再的规格值,进行push新的子集中 for (const i in obj) if (obj[i] !== '') arr.push(obj[i]) return arr } // 一个数组是否为sku数组的子集(parent 单个sku的数组) const isChildrenArr = (parent, child) => { // every()方法是js中的迭代方法,用于检测数组中的元素是否满足指定条件。 // 1、依次执行数组元素,如果一个元素不满足条件就返回false,不会继续执行后面的元素判断;所有数组元素都满足条件则返回true。 // 2、不会改变原数组。 // includes() 方法用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false。 /** ********** 思路:非常重要也是最关键的一步*********/ // 1. 找得到的情况: // 如: 当我开始没选中 child(建造子集数组的值)为 [男] // 然后遍历V等于男,当我 parent为['男', '白色', '100'],那么我的男在我里面存在,就返回为true // 2. 找不到的情况: // 如: 当我开始选中白色,那么我child(建造子集数组的值)是[男,白色] // 然后遍历V等于男,当我 parent为['男', '蓝色', '100'] // 首先我第一次遍历男 确实在我数组里面存在,然后再找到白色进去遍历,那么此时白色在我parent数组里面不存在。 // 那么就直接返回为false, 因为上面有说到 every()方法 如果一个元素不满足条件就返回false,不会继续执行后面的元素判断 return child.every(v => { return parent.includes(v) }) } // 获取规格值属性数组 const getCheckedStockList = (list, stockList, currentChoose) => list.map((group) => { // 遍历规格值 const items = group.items.map((item) => { // some()方法用于检测数组中的元素是否满足指定条件(函数提供)。 // some()方法会依次执行数组的每个元素: // 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。 // 如果没有满足条件的元素,则返回false。 // 注意some() 不会对空数组进行检测, 注意some() 不会改变原始数组。 // 检测数组中的元素是否满足指定条件 const hasStock = stockList.some(stock => { // 找出sku数组中符合条件的元素(子集节点找得到并且库存数量大于0)如果你的条件不是库存大于0 而是其它,请你自己改掉 后面的条件 return isChildrenArr(stock.key, getKey(currentChoose, group.name, item)) && stock.stock > 0 }) return { ...item, hasStock } }) return { ...group, items } }) // 无限级SKU选择(把方法拷贝,不是vue项目也能用哦,思想一样) export default { data () { return { // sku列表 stockList: [ { key: ['男', '白色', '100'], stock: 2 }, { key: ['男', '白色', '200'], stock: 0 }, { key: ['男', '红色', '100'], stock: 2 }, { key: ['男', '红色', '300'], stock: 1 }, { key: ['女', '红色', '200'], stock: 2 }, { key: ['女', '蓝色', '200'], stock: 2 }, { key: ['女', '白色', '200'], stock: 2 }, { key: ['女', '白色', '100'], stock: 2 } ], // 规格值数组(无限级SKU选择) list: [ { name: 'sex', items: [ { name: '男' }, { name: '女' } ] }, { name: 'color', items: [ { name: '白色' }, { name: '红色' }, { name: '蓝色' } ] }, { name: 'size', items: [ { name: '100' }, { name: '200' }, { name: '300' } ] } ], // 选中对象 currentChoose: { color: '', size: '', sex: '' } } }, created () { // 初始化规格 sku列表查询对应的路径 this.list = getCheckedStockList(this.list, this.stockList, this.currentChoose) }, methods: { // 规格属性点击事件 clickSku (groupName, item) { // 如果我当前的规格属性不能被选选中,那么直接返回 if (!item.hasStock) return // 取出对应的数据 const { currentChoose, stockList, list } = { stockList: this.stockList, list: this.list, currentChoose: this.currentChoose } // 如果我当前选中的规格值存在,在我当前选中项数组里面,那么就把选中项去掉,否则就把当前选中规格的key值赋值为当前选中的value值 const nextChoose = currentChoose[groupName] === item.name ? { ...currentChoose, [groupName]: '' } : { ...currentChoose, [groupName]: item.name } // 每次点击都需要去sku列表重新查询对应的路径 const checkedStockList = getCheckedStockList(list, stockList, nextChoose) // 赋值选中对象 this.currentChoose = nextChoose // 赋值规格 this.list = checkedStockList } } } </script> <style lang="less"> .wrap-sku { .activeStock {background-color: red;} .isStock {background-color: #fff;} .noStock {background-color: #eee;cursor: default !important;} .product-box { 1200px; display: block; margin: 0 auto; } .product-delcom { color: #323232; font-size: 26px; padding: 30px 0; } .product-footerlist li { border: 1px solid #606060; border-radius: 5px; color: #606060; text-align: center; padding: 10px 30px; list-style: none; float: left; margin-right: 20px; cursor: pointer; } } </style>
效果图: