目录
描述:
数据结构
组件代码文件
描述:
自动处理SKU数据生成规格属性列表 支持用户选择不同规格组合 智能禁用无库存选项 自动匹配当前选择对应的SKU信息 通过视觉样式区分可选/不可选状态 该组件采用Vue实现,通过计算属性和响应式数据实现规格联动效果,确保用户只能选择有库存的商品组合。组件封装了完整的规格选择逻辑,包括默认选中、库存检查等功能,可方便地集成到电商产品页面中。
常见的一个电商类需求,通过接口info数据,对每个尺寸、颜色图片、类型等分类进行一个动态的库存状态体现;如下图:
切换任何其中一类时,其余类型的库存状态都要更新;
数据结构
//这是目标数据源,此类需求后端都会把各种组合的数据一起发送过来;
"skuVoList": [{"skuNo": "94e407b8357776d6bf12b8b003b731fa","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01qa0bAp1sszJixAiom_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male l 47.5-62.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1111,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "95463e98bfe137333bd850353b047a6b","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01qa0bAp1sszJixAiom_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male xl 62.5-77.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1111,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "7e0ea0f5d0e7da4386dad1edd879f2e5","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01qa0bAp1sszJixAiom_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male xxl 77.5-85kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1111,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "61881bd26fcb4efbf7b885aac6a470e1","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01qa0bAp1sszJixAiom_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male 3xl 85-100kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1110,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "b458762fbcc6097776483d82e2e478fd","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01idIAyW1sszN1NUSy6_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female m 42.5-50kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1088,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "f6bcd50b02fc61268c0f9ab700cdaa75","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01idIAyW1sszN1NUSy6_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female l 50-60kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1037,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "9e28990f3456624e447225e039208aab","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01idIAyW1sszN1NUSy6_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female xl 60-72.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1050,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "002d6a1a3d4e68d5426ba071ce8b8ca3","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01idIAyW1sszN1NUSy6_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female xxl 72.5-77.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1098,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "55fdf2f3ace6682c6628769c83b4c520","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ye6IDs1sszN0nR9NI_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female xxl 72.5-77.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1110,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "e9a2cf23f82a9c19e158bea6530fc9e1","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ye6IDs1sszN0nR9NI_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male l 47.5-62.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1103,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "23797461456f7a6c397c5cddd5136141","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ye6IDs1sszN0nR9NI_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male xl 62.5-77.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1099,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "662c38947507f6af2b2c77a5a6179ea8","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ye6IDs1sszN0nR9NI_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male xxl 77.5-85kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1102,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "4bd6fd9bb2d46fee176e79a2f0455316","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long men-black","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ye6IDs1sszN0nR9NI_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Male 3xl 85-100kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1105,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "7d85a8e0d288f6eb2d4ac5df1b3994e9","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ULiilu1sszN1owOmf_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female m 42.5-50kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1108,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "590d033ce8a04346f11adc280d4991d6","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ULiilu1sszN1owOmf_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female l 50-60kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1104,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "2ac8fdaeedb5d7a592e8c4d473fe2e99","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ULiilu1sszN1owOmf_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female xl 60-72.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1109,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null},{"skuNo": "7749db94c8cade7be876b3e12d8c9b4a","spuNo": "872585659100","channel": "1688","skuAttr": [{"code": "3216","name": "Color","enumValue": "Crow heart double long lady-white","enumImageUrl": "https://cbu01.alicdn.com/img/ibank/O1CN01ULiilu1sszN1owOmf_!!2201499165823-0-cib.jpg"},{"code": "450","name": "Size","enumValue": "Female xxl 72.5-77.5kg","enumImageUrl": null}],"price": null,"priceCNY": null,"stock": 1111,"priceMode": null,"discount": null,"originalPrice": null,"originalPriceCNY": null}],
组件代码文件
//commodity-specification.vue-组件文件
<template><div class="select-type"><div v-for="item in skuAttributes" :key="item.code" class="comm-size"><span class="label">{{ item.name }}:<span class="value">{{ selectedValues[item.name] }}</span></span><div class="size-list"><div v-for="infoItem in item.list" :key="infoItem.enumValue" class="size-item" :class="{'active': isSelected(item.name, infoItem.enumValue),'disabled': isDisabled(item.name, infoItem.enumValue)}"@click="!isDisabled(item.name, infoItem.enumValue) && selectAttribute(item.name, infoItem.enumValue, infoItem)"><template v-if="infoItem.enumImageUrl"><div class="goods-image" :class="{'active': isSelected(item.name, infoItem.enumValue),'disabled': isDisabled(item.name, infoItem.enumValue)}"><img :src="infoItem.enumImageUrl"><span v-if="isDisabled(item.name, infoItem.enumValue)">{{ $t('user.outStock') }}</span></div></template><template v-else><div class="goods-text" :class="{'active': isSelected(item.name, infoItem.enumValue),'disabled': isDisabled(item.name, infoItem.enumValue)}">{{ infoItem.enumValue }}<span v-if="isDisabled(item.name, infoItem.enumValue)">{{ $t('user.outStock') }}</span></div></template></div></div></div></div>
</template><script>
export default {name: 'commodity-specification',props: {info: Object},data() {return {selectedValues: {}, // 用户选中的属性值skus: [], // 从数据源填充的SKU列表selectedAttributes: {}, // 用户选中的属性skuAttributes: [], // SKU属性及其值的列表selectedProductDetails: null // 选中的SKU详细信息};},mounted() {this.processSkus(this.info);},methods: {processSkus(data) {// 处理原始SKU数据并生成属性列表if (data.skuVoList.length > 0) {data.skuVoList.forEach(sku => {const attributes = {};sku.skuAttr.forEach(attr => {attributes[attr.name] = attr.enumValue;if (!this.skuAttributes.find(a => a.name === attr.name)) {this.skuAttributes.push({name: attr.name, // 商品名称code: attr.code, // 商品编号list: []});}const attribute = this.skuAttributes.find(a => a.name === attr.name);// 检查是否已存在相同的属性值,避免重复if (!attribute.list.find(l => l.enumValue === attr.enumValue)) {attribute.list.push({enumValue: attr.enumValue,enumImageUrl: attr.enumImageUrl});}});this.skus.push({skuNo: sku.skuNo,spuNo: sku.spuNo,attributes,image: sku.skuAttr.find(attr => attr.enumImageUrl)?.enumImageUrl || '',stock: sku.stock,price: sku.price,priceCNY: sku.priceCNY,originalPriceCNY: sku.originalPriceCNY || 0,originalPrice: sku.originalPrice || 0});});// 处理完成后默认选中第一个有库存的选项if (this.skuAttributes.length > 0) {this.autoSelectFirstAvailable();}}},// 自动选择第一个有库存的组合autoSelectFirstAvailable() {// 重置选择状态this.selectedAttributes = {};this.selectedValues = {};// 按顺序为每个属性选择第一个可用的值for (let i = 0; i < this.skuAttributes.length; i++) {const attribute = this.skuAttributes[i];// 查找当前属性下第一个有库存的选项for (let j = 0; j < attribute.list.length; j++) {const infoItem = attribute.list[j];// 模拟选择该属性const tempSelected = { ...this.selectedAttributes, [attribute.name]: infoItem.enumValue };// 检查当前选择是否能匹配到有库存的SKUif (this.hasAvailableSku(tempSelected)) {this.selectAttribute(attribute.name, infoItem.enumValue, infoItem);break;}}}// 如果所有属性都选择了,但还没有匹配到具体SKU,则尝试找到一个完整的匹配if (Object.keys(this.selectedAttributes).length === this.skuAttributes.length && !this.selectedProductDetails) {this.updateSelectedProductDetails();}},selectAttribute(name, value, infoItem) {// 更新选中状态this.selectedAttributes = { ...this.selectedAttributes, [name]: value };this.selectedValues = { ...this.selectedValues, [name]: infoItem.enumValue };// 检查是否所有属性都已选择if (this.allAttributesSelected()) {this.updateSelectedProductDetails();}// 触发更新其他属性的可选状态(在模板重新渲染后)this.$nextTick(() => {// 这里不需要额外操作,因为 isDisabled 是计算属性会自动更新});},updateSelectedProductDetails() {const selectedProduct = this.skus.find(sku => {return Object.entries(this.selectedAttributes).every(([key, val]) => sku.attributes[key] === val);});if (selectedProduct && selectedProduct.stock > 0) {this.selectedProductDetails = {...selectedProduct,attributes: this.selectedAttributes};this.$emit('on-change', this.selectedProductDetails);}},isSelected(name, value) {return this.selectedAttributes[name] === value;},// 检查选项是否禁用(根据当前已选属性和其他SKU的库存情况)isDisabled(name, value) {// 创建一个临时的选中状态,包含当前要检查的选项const tempSelected = { ...this.selectedAttributes, [name]: value };// 检查是否存在匹配的有库存SKUreturn !this.hasAvailableSku(tempSelected);},// 检查给定的属性组合是否存在有库存的SKUhasAvailableSku(selectedAttrs) {return this.skus.some(sku => {// 检查SKU是否有库存if (sku.stock <= 0) return false;// 检查SKU是否匹配当前选中的属性return Object.entries(selectedAttrs).every(([attrName, attrValue]) => {return sku.attributes[attrName] === attrValue;});});},allAttributesSelected() {// 检查是否所有属性都已选择const allAttributes = this.skuAttributes.map(attr => attr.name);return allAttributes.every(attr => this.selectedAttributes[attr]);}}
};
</script><style scoped lang="less">
.select-type {.comm-size {.label {display: block;margin-right: 6px;margin-bottom: 6px;font-family: PingFang HK, PingFang HK;font-size: 14px;line-height: 1.5;margin-bottom: 16px;margin-top: 16px;.value {font-weight: 600;}}.size-list {display: block;overflow: hidden;.size-item {.goods-image {float: left;display: flex;align-items: center;justify-content: center;width: 55px;height: 55px;border-radius: 6px;opacity: 1;border: 1px solid #DCDEE2;overflow: hidden;margin-right: 10px;margin-bottom: 10px;cursor: pointer;object-fit: cover;position: relative;>span {position: absolute;top: 0px;left: 50%;transform: translateX(-50%);background-color: #EEEEEE;color: #5A6572;white-space: nowrap;display: inline-block;width: 100%;text-align: center;font-size: 10px;}img {width: 100%;}}.goods-text {min-width: 60px;float: left;padding: 4px 12px;background: #FFFFFF;border-radius: 6px;opacity: 1;border: 1px solid #DCDEE2;box-sizing: border-box;cursor: pointer;color: @t-title-color;font-size: 14px;font-weight: 400;margin-right: 10px;margin-bottom: 15px;position: relative;text-align: center;>span {position: absolute;top: 0px;right: -1px;transform: translateY(-60%);background-color: #EEEEEE;color: #5A6572;white-space: nowrap;font-size: 10px;padding: 0px 3px;}}.active {border: 1px solid #333333;font-weight: 600;}// 禁用状态样式.disabled,.goods-image.disabled,.goods-text.disabled {cursor: not-allowed;opacity: 0.5;border-color: #DCDEE2;color: #999;}// 禁用状态下的活动样式.disabled.active {border: 1px solid #DCDEE2;font-weight: 400;}}}}
}
</style>