vue3 之 商城项目—封装SKU组件

认识SKU组件

在这里插入图片描述
SKU组件的作用
在这里插入图片描述
产出当前用户选择的商品规格,为加入购物车操作提供数据信息,在选择的过程中,组件的选中状态要进行更新,组件还要提示用户当前规格是否禁用,每次选择都要产出对应的sku数据

SKU组件的使用
假如你在开发的过程中要使用别人开发好的组件,重点看什么?
props和emit,props决定了当前组件接收什么数据,emit决定了会产出什么数据
在这里插入图片描述

点击规格

在这里插入图片描述
准备模版渲染规格数据

<script setup>
import { onMounted, ref } from 'vue'
import axios from 'axios'
// 商品数据
const goods = ref({})
const getGoods = async () => {// 1135076  初始化就有无库存的规格// 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)const res = await axios.get('http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1369155859933827074')goods.value = res.data.result
}
onMounted(() => getGoods())</script><template><div class="goods-sku"><dl v-for="item in goods.specs" :key="item.id"><dt>{{ item.name }}</dt><dd><template v-for="val in item.values" :key="val.name"><!-- 图片类型规格 --><img v-if="val.picture" :src="val.picture" :title="val.name"><!-- 文字类型规格 --><span v-else>{{ val.name }}</span></template></dd></dl></div>
</template><style scoped lang="scss">
@mixin sku-state-mixin {border: 1px solid #e4e4e4;margin-right: 10px;cursor: pointer;&.selected {border-color: #27ba9b;}&.disabled {opacity: 0.6;border-style: dashed;cursor: not-allowed;}
}.goods-sku {padding-left: 10px;padding-top: 20px;dl {display: flex;padding-bottom: 20px;align-items: center;dt {width: 50px;color: #999;}dd {flex: 1;color: #666;>img {width: 50px;height: 50px;margin-bottom: 4px;@include sku-state-mixin;}>span {display: inline-block;height: 30px;line-height: 28px;padding: 0 20px;margin-bottom: 4px;@include sku-state-mixin;}}}
}
</style>

选中和取消选中实现

基本思路:

  1. 每一个规格按钮都拥有自己的选中状态数据-selected,true为选中,false为取消选中
  2. 配合动态class,把选中状态selected作为判断条件,true让active类名显示,false让active类名不显示
  3. 点击的是未选中,把同一个规格的其他取消选中,当前点击项选中;点击的是已选中,直接取消
<script setup>
// 省略代码
// 选中和取消选中实现
const changeSku = (item, val) => {// 点击的是未选中,把同一个规格的其他取消选中,当前点击项选中,点击的是已选中,直接取消if (val.selected) {val.selected = false} else {item.values.forEach(valItem => valItem.selected = false)val.selected = true}
}
</script><template><div class="goods-sku"><dl v-for="item in goods.specs" :key="item.id"><dt>{{ item.name }}</dt><dd><template v-for="val in item.values" :key="val.name"><img v-if="val.picture" @click="changeSku(item, val)" :class="{ selected: val.selected }" :src="val.picture":title="val.name"><span v-else @click="changeSku(val)" :class="{ selected: val.selected }">{{ val.name }}</span></template></dd></dl></div>
</template>

点击规格更新禁用状态—生成有效路径字典

规格禁用判断的依据是什么?
核心原理:当前的规格sku,或者组合起来的规格sku,在skus数组中对应的库存为0时,当前规格会被禁用,生成路径字典是为了协助和简化这个匹配过程
在这里插入图片描述
实现步骤
1️⃣根据库存字段得到有效的sku数组
2️⃣根据有效的sku数组使用powerSet算法得到所有子集
3️⃣根据子集生成路径字典对象
生成有效路径字典对象
在这里插入图片描述

import powerSet from './power-set'
const getPathMap = (goods) => {const pathMap = {}// 1. 得到所有有效的Sku集合 const effectiveSkus = goods.skus.filter(sku => sku.inventory > 0)// 2. 根据有效的Sku集合使用powerSet算法得到所有子集 [1,2] => [[1], [2], [1,2]]effectiveSkus.forEach(sku => {// 2.1 获取匹配的valueName组成的数组const selectedValArr = sku.specs.map(val => val.valueName)// 2.2 使用算法获取子集const valueArrPowerSet = powerSet(selectedValArr)// 3. 根据子集生成最终的路径字典对象// 3.1 遍历子集 往pathMap中插入数据valueArrPowerSet.forEach(arr => {// 根据Arr得到字符串的key,约定使用-分割 ['蓝色','美国'] => '蓝色-美国'//初始化key 数组join -> 字符串  对象的keyconst key = arr.join('-')//如果已经存在当前key了 就往数组中直接添加skuId 如果不存在key 直接做赋值 if (pathMap[key]) {pathMap[key].push(sku.id)} else {pathMap[key] = [sku.id]}})})return pathMap
}
// 数据获取完毕生成路径字典
let pathMap = {}
const getGoods = async () => {// 1135076  初始化就有无库存的规格// 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)const res = await axios.get('http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1135076')goods.value = res.data.resultpathMap = getPathMap(goods.value)// 初始化更新按钮状态initDisabledState(goods.value.specs, pathMap)
}

power-set.js

export default function bwPowerSet (originalSet) {const subSets = []// We will have 2^n possible combinations (where n is a length of original set).// It is because for every element of original set we will decide whether to include// it or not (2 options for each set element).const numberOfCombinations = 2 ** originalSet.length// Each number in binary representation in a range from 0 to 2^n does exactly what we need:// it shows by its bits (0 or 1) whether to include related element from the set or not.// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to// include only "2" to the current set.for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {const subSet = []for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {// Decide whether we need to include current element into the subset or not.if (combinationIndex & (1 << setElementIndex)) {subSet.push(originalSet[setElementIndex])}}// Add current subset to the list of all subsets.subSets.push(subSet)}return subSets
}

点击规格更新禁用状态—初始化规格禁用

思路:遍历每一个规格对象,使用nam字段作为key去路径字典pathMap中做匹配,匹配不上则禁用
怎么做到显示上的禁用呢?
1️⃣通过增加disabled字段,匹配上路径字段,disabled为false,匹配不上路径字段,disabled为true
2️⃣配合动态类名控制禁用类名

// 1. 定义初始化函数
// specs:商品源数据 pathMap:路径字典
const initDisabledState = (specs, pathMap) => {// 约定:每一个按钮的状态由自身的disabled进行控制specs.forEach(item => {item.values.forEach(val => {// 路径字典中查找是否有数据 有-可以点击 没有-禁用val.disabled = !pathMap[val.name]})})
}// 2. 在数据返回后进行初始化处理
let patchMap = {}
const getGoods = async () => {// 1135076  初始化就有无库存的规格// 1369155859933827074 更新之后有无库存项(蓝色-20cm-中国)const res = await axios.get('http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1135076')goods.value = res.data.resultpathMap = getPathMap(goods.value)// 初始化更新按钮状态initDisabledState(goods.value.specs, pathMap)
}

点击规格更新禁用状态—点击时组合禁用更新

思路(点击规格时)
1️⃣按照顺序得到规格选项中的数组[‘蓝色’,‘20cm’,‘undefined’]
2️⃣遍历每一个规格
把name字段的值填充到对应的位置
过滤掉undefined项使用join方法形成一个有效的key
使用key去pathMap中进行匹配,匹配不删,则当前项禁用
在这里插入图片描述
在这里插入图片描述

结合上面图片,蓝色-20cm-中国 在数组中是没有的,所以中国应该禁用掉

在这里插入图片描述

// 获取选中项的匹配数组
const getSelectedArr = (specs) => {const selectedArr = []specs.forEach((spec, index) => {//目标:找到valus中selected为true的项,然后把它的name字段添加数组对应的位置const selectedVal = spec.values.find(val => val.selected)if (selectedVal) {selectedArr[index] = selectedVal.name} else {selectedArr[index] = undefined}})return selectedArr
}
// 更新按钮的禁用状态
const updateDisabledStatus = (specs, pathMap) => {// 遍历每一种规格specs.forEach((item, i) => {// 拿到当前选择的项目const selectedArr = getSelectedArr(specs)// 遍历每一个按钮item.values.forEach(val => {if (!val.selected) {selectedArr[i] = val.name// 去掉undefined之后组合成keyconst key = selectedArr.filter(value => value).join(spliter)val.disabled = !pathMap[key]}})})
}

产出有效的SKU信息

在这里插入图片描述
什么是有效的SKU?
颜色 尺寸 产地都选择了

如何判断当前用户已经选择了所有有效的规格?
已选择项数组[‘蓝色’,‘20cm’,‘undefined’]中找不到undefined,那么用户已经选择了所有的有效规格,此时可以产出数据

如何获取当前SKU信息对象?
把已选择项数组拼接为路径字典的key,去路径字典pathMap中找即可

//产出SKU对象数据
const index = getSelectedArr(goods.value.specs).findIndex(item=>item === undefined)
if(index>-1){console.log('找到了,信息不完整')}else{console.log('没找到了,信息完整,可以产出')//获取SKU对象getSelectedArr(goods.value.specs).join('*')const skuIds = pathMap[key]console.log('sku对象',skuIds)//以skuIds作为匹配项去goods.value.skus数组中找}

在这里插入图片描述

完整代码

Xtxsku/index.vue

<template><div class="goods-sku"><dl v-for="item in goods.specs" :key="item.id"><dt>{{ item.name }}</dt><dd><template v-for="val in item.values" :key="val.name"><img :class="{ selected: val.selected, disabled: val.disabled }" @click="clickSpecs(item, val)"v-if="val.picture" :src="val.picture" /><span :class="{ selected: val.selected, disabled: val.disabled }" @click="clickSpecs(item, val)" v-else>{{val.name}}</span></template></dd></dl></div>
</template>
<script>
import { watchEffect } from 'vue'
import getPowerSet from './power-set'
const spliter = '★'
// 根据skus数据得到路径字典对象
const getPathMap = (skus) => {const pathMap = {}if (skus && skus.length > 0) {skus.forEach(sku => {// 1. 过滤出有库存有效的skuif (sku.inventory) {// 2. 得到sku属性值数组const specs = sku.specs.map(spec => spec.valueName)// 3. 得到sku属性值数组的子集const powerSet = getPowerSet(specs)// 4. 设置给路径字典对象powerSet.forEach(set => {const key = set.join(spliter)// 如果没有就先初始化一个空数组if (!pathMap[key]) {pathMap[key] = []}pathMap[key].push(sku.id)})}})}return pathMap
}// 初始化禁用状态
function initDisabledStatus (specs, pathMap) {if (specs && specs.length > 0) {specs.forEach(spec => {spec.values.forEach(val => {// 设置禁用状态val.disabled = !pathMap[val.name]})})}
}// 得到当前选中规格集合
const getSelectedArr = (specs) => {const selectedArr = []specs.forEach((spec, index) => {const selectedVal = spec.values.find(val => val.selected)if (selectedVal) {selectedArr[index] = selectedVal.name} else {selectedArr[index] = undefined}})return selectedArr
}// 更新按钮的禁用状态
const updateDisabledStatus = (specs, pathMap) => {// 遍历每一种规格specs.forEach((item, i) => {// 拿到当前选择的项目const selectedArr = getSelectedArr(specs)// 遍历每一个按钮item.values.forEach(val => {if (!val.selected) {selectedArr[i] = val.name// 去掉undefined之后组合成keyconst key = selectedArr.filter(value => value).join(spliter)val.disabled = !pathMap[key]}})})
}export default {name: 'XtxGoodSku',props: {// specs:所有的规格信息  skus:所有的sku组合goods: {type: Object,default: () => ({ specs: [], skus: [] })}},emits: ['change'],setup (props, { emit }) {let pathMap = {}watchEffect(() => {// 得到所有字典集合pathMap = getPathMap(props.goods.skus)// 组件初始化的时候更新禁用状态initDisabledStatus(props.goods.specs, pathMap)})const clickSpecs = (item, val) => {if (val.disabled) return false// 选中与取消选中逻辑if (val.selected) {val.selected = false} else {item.values.forEach(bv => { bv.selected = false })val.selected = true}// 点击之后再次更新选中状态updateDisabledStatus(props.goods.specs, pathMap)// 把选择的sku信息传出去给父组件// 触发change事件将sku数据传递出去const selectedArr = getSelectedArr(props.goods.specs).filter(value => value)// 如果选中得规格数量和传入得规格总数相等则传出完整信息(都选择了)// 否则传出空对象if (selectedArr.length === props.goods.specs.length) {// 从路径字典中得到skuIdconst skuId = pathMap[selectedArr.join(spliter)][0]const sku = props.goods.skus.find(sku => sku.id === skuId)// 传递数据给父组件emit('change', {skuId: sku.id,price: sku.price,oldPrice: sku.oldPrice,inventory: sku.inventory,specsText: sku.specs.reduce((p, n) => `${p} ${n.name}${n.valueName}`, '').trim()})} else {emit('change', {})}}return { clickSpecs }}
}
</script><style scoped lang="scss">
@mixin sku-state-mixin {border: 1px solid #e4e4e4;margin-right: 10px;cursor: pointer;&.selected {border-color: $xtxColor;}&.disabled {opacity: 0.6;border-style: dashed;cursor: not-allowed;}
}.goods-sku {padding-left: 10px;padding-top: 20px;dl {display: flex;padding-bottom: 20px;align-items: center;dt {width: 50px;color: #999;}dd {flex: 1;color: #666;>img {width: 50px;height: 50px;margin-bottom: 4px;@include sku-state-mixin;}>span {display: inline-block;height: 30px;line-height: 28px;padding: 0 20px;margin-bottom: 4px;@include sku-state-mixin;}}}
}
</style>

Xtxsku/power-set.js


export default function bwPowerSet (originalSet) {const subSets = []// We will have 2^n possible combinations (where n is a length of original set).// It is because for every element of original set we will decide whether to include// it or not (2 options for each set element).const numberOfCombinations = 2 ** originalSet.length// Each number in binary representation in a range from 0 to 2^n does exactly what we need:// it shows by its bits (0 or 1) whether to include related element from the set or not.// For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to// include only "2" to the current set.for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {const subSet = []for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {// Decide whether we need to include current element into the subset or not.if (combinationIndex & (1 << setElementIndex)) {subSet.push(originalSet[setElementIndex])}}// Add current subset to the list of all subsets.subSets.push(subSet)}return subSets
}

页面使用

<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
const goods = ref({})
const route = useRoute()
const getGoods = async () => {const res = await getDetail(route.params.id)goods.value = res.result
}
onMounted(() => getGoods())
// sku规格被操作时
let skuObj = {}
const skuChange = (sku) => {console.log(sku)skuObj = sku
}
</script><!-- sku组件 --><XtxSku :goods="goods" @change="skuChange" />

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/259374.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

物奇平台DRC动态范围控制修改方法

物奇平台DRC动态范围控制修改 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙耳机音频,DSP音频项目核心开发资料, 音频 DRC 是指动态范围控制(Dyna

suse15 sp3-sp5离线安装中安装FIO

没有网络的情况下&#xff0c;离线安装相对比较困难一点&#xff0c;所有需要提前下载相应的RPM安装包 FIO 安装包链接如下&#xff1a; Install package benchmark / fio 正常安装的时候&#xff0c;会出现问题 如下&#xff1a; google下 https://opensuse.pkgs.org/15.5/…

Spring Boot 笔记 024 登录页面

1.1 登录接口 //导入request.js请求工具 import request from /utils/request.js//提供调用注册接口的函数 export const userRegisterService (registerData)>{//借助于UrlSearchParams完成传递const params new URLSearchParams()for(let key in registerData){params.a…

IOS破解软件安装教程

对于很多iOS用户而言&#xff0c;获取软件的途径显得较为单一&#xff0c;必须通过App Store进行下载安装。 这样的限制&#xff0c;时常让人羡慕安卓系统那些自由下载各类版本软件的便捷。 心中不禁生出疑问&#xff1a;难道iOS世界里&#xff0c;就不存在所谓的“破解版”软件…

vue-自定义创建项目(六)

为什么要自定义创建项目&#xff1f; 因为VueCli默认创建的项目不能够满足我们的要求&#xff0c;比如默认的项目中没有帮我们集成路由&#xff0c;vuex&#xff0c;eslink等功能。 默认项目 自定义创建项目 流程&#xff1a; 创建项目命令&#xff1a;vue create custom_dem…

【嵌入式学习】IO网络接口day02.18

1.使用fgets统计给定文件的行数 #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, const char *argv[]) {FILE *fpNULL;if((fpfopen("./test1.txt","r"))NULL){perror("错误信息");return -1…

C语言从零实现贪吃蛇小游戏

制作不易&#xff0c;点赞关注一下呗&#xff01;&#xff01;&#xff01; 文章目录 前言一. 技术要点二、WIN32API介绍三、贪吃蛇游戏设计与分析 1.游戏开始前的初始化 2.游戏运行的逻辑 总结 前言 当我们掌握链表这样的数据结构之后&#xff0c;我们就可以用它来…

Go语言每日一练——链表篇(八)

传送门 牛客面试笔试必刷101题 ----------------两个链表的第一个公共结点 题目以及解析 题目 解题代码及解析 解析 这一道题使用的还是双指针算法&#xff0c;我们先求出两个链表的长度差n&#xff0c;然后定义快慢指针&#xff0c;让快指针先走n步&#xff0c;最后快慢指…

基于SSM的社区疫情防控管理系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的社区疫情防控管理系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spri…

拿捏c语言指针(中)

前言 书接上回 拿捏c语言指针&#xff08;上&#xff09; 此篇主要讲解的是指针与数组之间的爱恨情仇&#xff0c;跟着我的脚步一起来看看吧~ 创造不易&#xff0c;可以帮忙点点赞吗 如有差错&#xff0c;欢迎指出 理解数组名 数组名是首元素地址 例外 1.sizeof&#xff0…

C#使用迭代器实现文字的动态效果

目录 一、涉及到的知识点 1.GDI 2.Thread类 3.使用IEnumerable()迭代器 二、实例 1.源码 2.生成效果&#xff1a; 一、涉及到的知识点 1.GDI GDI主要用于在窗体上绘制各种图形图像。 GDI的核心是Graphics类&#xff0c;该类表示GDI绘图表面&#xff0c;它提供将对象绘制…

在UE5中使用体积材质

在平时使用UE的材质设置时&#xff0c;经常会看见Material Domain Volume类型&#xff0c;但是却很少使用。其实该类型可以配合体积雾使用&#xff0c;并制作体积效果以弥补自带雾参数的不足。 操作流程 首先找到场景中的ExponentialHeightFog组件&#xff0c;开启体积雾Volu…

c++阶梯之类与对象(下)

前文&#xff1a; c阶梯之类与对象&#xff08;上&#xff09;-CSDN博客 c阶梯之类与对象&#xff08;中&#xff09;-CSDN博客 c阶梯之类与对象&#xff08;中&#xff09;&#xff1c; 续集 &#xff1e;-CSDN博客 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&a…

Momentum2

攻击机 192.168.223.128 目标机 192.168.223.147 主机发现 nmap -sP 192.168.223.0/24 端口扫描 nmap -sV -A -p- 192.168.223.147 开启了22 80 端口 看一下web界面 源码&#xff0c;robots.txt ,url都观察了一下好像没什么有用信息 扫一下目录 gobuster dir -u http:…

C++面试宝典第28题:寻找丢失的数字

题目 给定一个包含n个整数的数组nums,其中nums[i]在区间[1, n]内。请找出所有在[1, n]范围内,但没有出现在nums中的数字,并以数组的形式返回结果。 示例1: 输入:nums = [4, 3, 2, 7, 8, 2, 3, 1] 输出:[5, 6] 示例2: 输入:nums = [1, 1] 输出:[2] 解析 初看这道题,…

VFH特征的使用(一)

一、SHOT特征描述符可视化 C #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> #include <pcl/features/normal_3d_omp.h> #include <pcl/registration/correspondence_estimation.h> #include <boo…

『 C++ 』海量数据处理

文章目录 &#x1f996; 快速找出海量数据中是否存在该整型数据&#x1f996; 有限内存情况下两个文件(海量query)中找出交集&#x1f996; 海量数据中找出只出现1次的数据&#x1f996; 有限内存情况下两个文件(整型)找出交集&#x1f996; 海量数据中找出出现次数不超过2次的…

挑战杯 YOLOv7 目标检测网络解读

文章目录 0 前言1 yolov7的整体结构2 关键点 - backbone关键点 - head3 训练4 使用效果5 最后 0 前言 世界变化太快&#xff0c;YOLOv6还没用熟YOLOv7就来了&#xff0c;如果有同学的毕设项目想用上最新的技术&#xff0c;不妨看看学长的这篇文章&#xff0c;学长带大家简单的…

5G——小区搜索流程

小区搜索流程 小区搜索目标&#xff1a;读取到SIB1. 小区搜索流程概述&#xff1a;SIB1在PDSCH信道承载&#xff0c;承载SIB1的信道在哪个位置由PDCCH告诉&#xff0c;而PDCCH的基本信息由MIB告诉&#xff0c;MIB信息由广播信道PBCH广播出去&#xff0c;物理信道解调需要解调…

vue3-组合式 API

什么是组合式 API&#xff1f; 组合式 API (Composition API) 是一系列 API 的集合&#xff0c;使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语&#xff0c;涵盖了以下方面的 API&#xff1a; 响应式 API&#xff1a;例如 ref() 和 reactive()&a…