vue3实现商城系统详情页(前端实现)

目录

写在前面

预览

实现

图片部分

详情部分

代码

源码地址

总结


写在前面

笔者不是上一个月毕业了么?找工作没找到,准备在家躺平两个月。正好整理一下当时的毕业设计,是一个商城系统。还是写篇文章记录下吧

预览

商品图片切换显示

sku规格切换

文章主要描述左侧图片组件,右侧sku的切换实现

在阅读本篇文章之前,你需要了解的是SPU和SKU是什么。

实现

图片部分

图片部分组件代码

<template><div><div style="display: flex;justify-content: center"><!--      大图--><el-image :src="$img+showBigImg" style="width: 400px; height: 400px"><template #error><div class="image-slot"><el-icon><icon-picture/></el-icon></div></template></el-image></div><!--    小图--><div style="display: flex;justify-content: space-between;margin-top: 20px"><el-icon style="width: 50px;height: 50px" @click="last"><ArrowLeftBold/></el-icon><div style="display: flex;justify-content: center;"><div v-for="item in showList" :key="item" :class="item===showBigImg ? 'is_show' : 'not_show' "@mouseover="show(item)" @click="show(item)" style="margin-left: 20px"><el-image :src="$img+item" fit="fill" style="width: 50px;height: 50px"/></div></div><el-icon @click="next" style="width: 50px;height: 50px"><ArrowRightBold/></el-icon></div></div>
</template>
<script setup>
import {ref, defineProps, watch,} from 'vue'let props = defineProps({imgList: {type: Array,default: () => []}
})
let showBigImg = ref(props.imgList[0])//页面初始化的时候显示第一个图片
let pageNum = ref(0)//当前展示图片处于多少页
let showList = ref(props.imgList.slice(0, 4))//初始化只展示前四个function show(big) {showBigImg.value = big
}
//下一页
function last() {
//  通过判断showBigImg的值来判断当前处于元素if (Array.isArray(showList.value)) {//查找索引,判断是否是最后一个元素(注意此处是原数组)if (pageNum.value === 0) {return}pageNum.value--} else {console.log("不是数组类型")}
}
//上一页
function next() {if (Array.isArray(props.imgList)) {//计算最大的页码,向上取整let mixPage = Math.ceil(props.imgList.length / 4);if ((pageNum.value + 1) === mixPage) {//如果是最后一页,则不能翻页return}//如果不是最后一页,则翻页pageNum.value++} else {console.log("不是数组类型")}
}
watch(() => props.imgList, () => {//监听父组件传递数据的变化,修改展示的数据showBigImg.value=props.imgList[0]showList.value = props.imgList.slice(0, 4)
})
watch(pageNum, () => {
//  监听pageNum的值变换,修改showList的显示数据showList.value = props.imgList.slice(pageNum.value * 4, (pageNum.value + 1) * 4)showBigImg.value = showList.value[0]
})
</script><style scoped>
.not_show {width: 50px;height: 50px;border: 1px;
}.is_show {width: 50px;height: 50px;border: solid 3px #098CC0;
}
</style>

注:

  1. 父组件需要控制这个图片组件的渲染时机,最好保证图片数据获取到了之后再加载组件。虽然,子组件已经监听了prop的数据,但是为了避免问题,还是尽量数据获取之后再加载组件
  2. 需要监听父组件传递的图片路径数据变化,当sku变化的时候,父组件会传递的图片路径也会变化。

详情部分

详情部分思路比较多,我之前周实训也是写的商城,那个时候的思路是页面显示SKU的属性名称。每点击一个SKU销售属性值,就将当前选中的SKU销售属性值发送给后端,然后后端去数据库中查找哪个SKU具有这两个属性值(后端计算)。然后再返回这个sku的信息。当时是以SPU为主。即首页展示的商品信息都是SPU。点击SPU后获取这个SPU下的所有SKU的属性组合,并随机获取一个SKU的信息用于初始展示。(如果读者想了解我之前的写法的话,请回复评论,我可以去找找,毕竟有将近两年了)

这次参考谷粒商城的方式后,采用的是以SKU为主,即首页展示的商品列表都是SKU。点击一个SKU后查询SPU的所有SKU属性值。当用户点击属性时,然后前端需要计算当前选中的是哪一个sku,然后再请求后端获取数据

代码

老样子先粘代码,再解读

<template><div><el-skeleton :loading="loading" animated><template #default><div class="detail_sku_box"><el-row :gutter="20">
<!--            左侧商品图部分--><el-col :span="8" :offset="1"><img-list :imgList="imgUrlList" v-if="showImg"></img-list></el-col>
<!--            右侧SKU信息以及销售属性部分--><el-col :span="14"><div><h2>{{ skuItemInfo?.skuInfo?.skuTitle }}</h2><h5>{{ skuItemInfo?.skuInfo?.skuSubtitle }}</h5><div><span class="price_class">¥{{ skuItemInfo?.skuInfo?.price }}</span></div><div style="margin-top: 20px"><div v-for="item in skuItemInfo?.skuItemSaleAttrVos" :key="item.id"><el-row :gutter="20"><el-col :span="3">{{ item.attrName }}</el-col><el-col :span="4" v-for="(valueItem,i) in item.attValues" :key="i"><button:class="valueItem?.skuIds.indexOf(skuItemInfo.skuInfo?.id)!=-1 ? 'skuValue_act_class' : 'skuValue_inc_class'"@click="selectItem(item,valueItem.skuIds)">{{ valueItem.attrValue }}</button></el-col></el-row><hr></div></div><div style="margin-top: 50px">库存:{{ skuItemInfo.skuStock }}</div><div style="margin-top: 50px"><el-input-number v-model="num"/></div><div style="margin-top: 50px"><button class="button button2" @click="addCart"><span>加入购物车</span></button><button class="button button2" @click="toConfirmOrder"><span>立即购买</span></button><button class="button button2" @click="goToCustomerService"><span>联系客服</span></button></div></div></el-col></el-row></div>
<!--        下侧SPU、规格、售后、评价部分--><div class="msg_box"><div><el-row><el-col :span="4" :class=" showComponentData==0 ? 'active_class': 'no_active'"><span@click="showComponents(0)">商品介绍</span></el-col><el-col :span="4" :class=" showComponentData==1 ? 'active_class': 'no_active'"><span@click="showComponents(1)">规格与包装</span></el-col><el-col :span="4" :class=" showComponentData==2 ? 'active_class': 'no_active'"><span@click="showComponents(2)">售后保障</span></el-col><el-col :span="4" :class=" showComponentData==3 ? 'active_class': 'no_active'"><span@click="showComponents(3)">商品评价</span></el-col></el-row><hr></div></div><div style="padding-left: 100px;"><!--      商品介绍--><spu-describesv-if="showComponentData==0":descImgUrl="spuDescImgUrl" :descInfo="skuItemInfo"></spu-describes><!--      规格与包装--><spu-specification:group-data="skuItemInfo.groupAttrs"v-else-if="showComponentData==1"></spu-specification>
<!--          商品评价--><div v-else-if="showComponentData===2"><div><el-image :src="require('@/assets/shouhou.jpg')"></el-image></div></div><appraise-list :skuId="skuItemInfo.skuInfo.id" v-else-if="showComponentData===3"/></div></template><!--      骨架屏内容--><template #template><div style="height: 100%"><el-row :gutter="20"><el-col :span="7" :offset="1"><el-skeleton-item variant="image" style="height: 500px"/></el-col><el-col :span="13" :offset="1"><div><el-skeleton-item variant="text" style="width: 100%;height: 40px"/><el-skeleton-item variant="text" style="width: 100%;height: 40px;margin-top: 20px"/><div style="margin-top: 30px"><span class="price_class"><el-skeleton-item variant="text" style="height: 30px;width: 80%"/></span></div><div style="margin-top: 150px"><div v-for="item in 2" :key="item"><el-row :gutter="20"><el-col :span="3"><el-skeleton-item variant="text" style="height: 30px"/></el-col><el-col :span="3" v-for="(i) in 3" :key="i"><el-skeleton-item variant="text" style="height: 30px"/></el-col></el-row></div></div></div></el-col></el-row></div></template></el-skeleton></div>
</template><script setup>
import SpuDescribes from '@/components/commodity/SpuDescribes'
import SpuSpecification from '@/components/commodity/SpuSpecification'
import AppraiseList from '@/components/commodity/AppraiseList'
import {ref, onMounted,} from "vue";
import {useRoute, useRouter} from "vue-router";
import {getSkuItemApi,} from "@/api/goods";
import ImgList from "@/components/commodity/ImgList";
import {addCartApi} from '@/api/cart'
import {ElMessage, ElMessageBox} from 'element-plus'let loading = ref(true)
const route = useRoute()
const router = useRouter()
let imgUrlList = ref([])
let skuItemInfo = ref({})
let spuDescImgUrl = ref()
let showComponentData = ref(-1)//由于子组件需要父组件传递数据,需要确保父组件数据准备好了再加载子组件
let num = ref(1)
let showImg = ref(false)onMounted(() => {if (route.query.skuId) {getSkuItem(route.query.skuId)}setTimeout(() => {loading.value = false}, 1000)
})function getSkuItem(skuId) {getSkuItemApi(skuId).then(res => {skuItemInfo.value = res.dataskuItemInfo.value?.skuItemSaleAttrVos.forEach(attr => {attr.attValues.forEach(item => {if (item.skuIds.indexOf(skuItemInfo.value.skuInfo.id) != -1) {attr.selectSkuList = [...item.skuIds]//赋值,但是不引用地址值}})})/*获取图片路径*/let imgIds = res.data.skuImags.map(item => {return item.imgId})imgUrlList.value = [...imgIds]showImg.value = true}).then(() => {showComponentData.value = 0//父组件数据已经准备妥当,加载子组件})
}/*** 添加到购物车*/
function addCart() {addCartApi(skuItemInfo.value.skuInfo?.id, num.value)/*TODO 是否需要跳转购物车列表*/
}function toConfirmOrder() {if (skuItemInfo.value.skuStock - num.value < 0) {return ElMessage({message: "库存不足", type: 'error'})}//前往确认订单页面,传递参数:选择的skuid,购买的件数let data = {skuId: skuItemInfo.value.skuInfo.id,//选择的skubuyNumber: num.value//购买的件数}router.push({path: '/order-confirm',query: {data: JSON.stringify([data])}})
}function selectItem(selectGroup, skuIds) {/*先保存当前选中元素的skuIds数组,然后计算*/selectGroup.selectSkuList = skuIds/*遍历所有的分组,获取选中的skuid,然后计算出交集*/let arr = skuItemInfo.value?.skuItemSaleAttrVos.map(item => {return item.selectSkuList})//得出当前的skuIdlet findSkuId = findIntersection(arr)getSkuItem(findSkuId)//修改路由上的query值router.push({query:{skuId:findSkuId}})
}/*** 获取传入的数组中的交集的值* @param arrays* @returns {null}*/
function findIntersection(arrays) {// 创建一个 Set 对象来存储所有数组中的唯一元素let set = new Set();// 遍历每个数组,将其中的元素添加到 Set 对象中for (let array of arrays) {for (let element of array) {set.add(element);}}// 找到交集元素,即唯一存在于所有数组中的元素let intersection = null;for (let element of set) {if (arrays.every(array => array.includes(element))) {intersection = element;break;}}return intersection;
}function goToCustomerService() {ElMessageBox.alert('功能还未实现', '警告', {confirmButtonText: '确定',})
}function showComponents(v) {showComponentData.value = v
}
</script><style scoped>
.price_class {font-size: 30px;color: red;
}.active_class {color: #288FC7;
}.msg_box {margin-top: 200px;padding-left: 100px;
}.detail_sku_box {height: 400px;/*background-color: #99a9bf;*/
}
.skuValue_act_class {background-color: #098CC0;/*color: red;*/
}
.no_active:hover{color: red;
}
.cart_btn_msg {color: white;
}.button {width: 150px;height: 50px;background-color: #288FC7;border: none;color: white;padding: 15px 32px;text-align: center;text-decoration: none;display: inline-block;font-size: 16px;margin: 4px 2px;cursor: pointer;-webkit-transition-duration: 0.4s;transition-duration: 0.4s;
}.button2:hover {box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19);background-color: #1DAEEE;color: red;
}
</style>

下面是后端响应的VO模型

将SKU的销售属性值组合封装起来

这个方式后端要轻松一点。后端只需要获取获取前端传递的skuId返回这个VO即可。

前端就比较麻烦了,需要在点击不同的销售属性值时,计算出这个组合的SKU是哪一个。

我们来看一下

当页面点击一个属性值的时候,怎么才能计算出他的SKU?这个还是有一定难度的,因为这个

是一个不固定长度的数组,有可能是一个,有可能是两个,有可能是三个、四个都有可能。虽然大多数情况只有两个,比如说手机就有两个:颜色,版本。假设再添加几个属性(分期、保修服务)就更多了。你可能问这有什么关系呢?计算sku需要用到吗?

别急,我们先看看这个销售属性值下面的skuIds是什么:他表示的是当前的属性值哪几个SKU具有!也就是说我们在计算sku的时候用的就是他来计算的!

当页面点击属性值的同时就能获取到这个skuIds值了,然后将其存起来。以供计算

比如当前只有两个属性,那么就会有两个skuIds数组。很简单,我们只需要计算这两个skuIds数组元素的交集即可。如果有三个属性,那么就会有三个skuIds数组,计算他们的交集。听起来很简单?我当时想了一会,没弄出来,最后丢给文心一言实现了(文心一言都错了三四次哈哈)。代码如下

说实话我现在也没懂,对算法这一块还处于文盲状态。不过能实现就行!

源码地址

毕业设计:轻松购: 使用vue3+springboot构建的商城系统,集前台用户和后台管理于一体,本项目已经部署在云服务器上, 访问地址: 前台系统:123.207.205.51 后台系统:123.207.205.51:8080 (gitee.com)[这里是图片009]https://gitee.com/zfb12345/my-graduation-project

总结

sku计算那里,是这个详情页面唯一的难点,其他的部分我就不多说了。如果读者觉得有哪些部分不全,或者想要了解其他部分,随时评论。

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

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

相关文章

Java深拷贝和浅拷贝区别?

大家好&#xff0c;我是锋哥。今天分享关于【Java深拷贝和浅拷贝区别?】面试题。希望对大家有帮助&#xff1b; Java深拷贝和浅拷贝区别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Java中&#xff0c;深拷贝&#xff08;Deep Copy&#xff09;和浅拷贝&am…

React 第十七节 useMemo用法详解

概述 useMemo 是React 中的一个HOOK&#xff0c;用于根据依赖在每次渲染时候缓存计算结果&#xff1b; 大白话就是&#xff0c;只有依赖项发生变化时候&#xff0c;才会重新渲染为新计算的值&#xff0c;否则就还是取原来的值&#xff0c;有点类似 vue 中的 computed 计算属性…

全国数据资源入表年度发展报告(2024)(附下载)

近日&#xff0c;在“数据要素暨第二届数据资产价值大会”上&#xff0c;青岛、潍坊、湖州、广西等地的数据资产登记评价中心&#xff0c;联合发布了《全国数据资源入表年度发展报告&#xff08;2024&#xff09;》。 报告内容包括全国数据资源入表的总体发展概述、政策指引、…

【Mybatis】MyBatis 探秘:#{} 与 ${} 参传差异解码,数据库连接池筑牢数据交互根基

前言 &#x1f31f;&#x1f31f;本期讲解关于Spring IOC&DI的详细介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么…

解锁 draw.io 流程图制作工具的强大功能与应用(1/2)

一、draw.io 简介 &#xff08;一&#xff09;基本概述 draw.io 是一款由 JGraph 公司开发的基于网页的在线图表绘制工具。它最大的优势之一就是无需进行繁琐的下载和安装步骤&#xff0c;只要打开浏览器&#xff0c;访问其官网&#xff0c;就能立即开始使用。无论是在 Window…

ai论文写作免费平台:五款AI论文写作辅助工具的对比与分析

随着人工智能技术的飞速发展&#xff0c;越来越多的AI工具应运而生&#xff0c;为学术写作带来了前所未有的便利。本文将对千笔AI论文、笔灵AI论文、Smodin、以及Notion AI等五款AI论文写作辅助工具进行全面的对比与分析&#xff0c;以帮助用户更好地了解这些工具的优势和特点&…

三、基于langchain使用Qwen搭建金融RAG问答机器人--检索增强生成

经过前面2节数据准备后&#xff0c;现在来构建检索 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings import os# 定义 Embeddings embeddings HuggingFaceEmbeddings(model_name"m3e-base")#…

数据仓库工具箱—读书笔记02(Kimball维度建模技术概述02、事实表技术基础)

Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 第二章前言部分作者提到&#xff1a;技术的介绍应该通过涵盖各种行业的熟悉的用例展开&#xff08;赞同…

fabric.js

目录 一、在canvas上画简单的图形 二、在canvas上用路径(Path)画不规则图形 三、在canvas上插入图片并设置旋转属性(angle) 四、让元素动起来(animate) 五、图像过滤器(filters)让图片多姿多彩 六、颜色模式(Color)和相互转换(toRgb、toHex) 七、对图形的渐变填充(Gradi…

Liinux下VMware Workstation Pro的安装,建议安装最新版本17.61

建议安装最新版本17.61&#xff0c;否则可能有兼容性问题 下载VMware Workstation安装软件 从官网网站下载 https://support.broadcom.com/group/ecx/productdownloads?subfamilyVMwareWorkstationPro 选择所需版本 现在最新版本是17.61&#xff0c;否则可能有兼容性问题…

压力测试Jmeter简介

前提条件&#xff1a;要安装JDK 若不需要了解&#xff0c;请直接定位到左侧目录的安装环节。 1.引言 在现代软件开发中&#xff0c;性能和稳定性是衡量系统质量的重要指标。为了确保应用程序在高负载情况下仍能正常运行&#xff0c;压力测试变得尤为重要。Apache JMeter 是一…

前端的知识(部分)

11 前端的编写步骤 第一步:在HTML的页面中声明方法 第二步:在<script>中定义一个函数,其中声明一个data来为需要的数据 赋值一个初始值 第三步:编写这个方法实现对应的功能

LSTM详解

1. LSTM设计 LSTM(长短期记忆网络)详解 长短期记忆网络(LSTM, Long Short-Term Memory) 是一种特殊的循环神经网络(RNN),特别适合处理和预测序列数据中的长时间依赖关系。LSTM 通过引入“门机制”(如输入门、遗忘门、输出门)来解决标准 RNN 在长时间序列任务中梯度消…

我在广州学 Mysql 系列之 数据类型和运算符详解

ℹ️大家好&#xff0c;我是&#x1f606;练小杰&#xff0c;今天主要学习 Mysql的数据类型以及运算符操作~~ 上周五学习了“Mysql 系列之 数据“表”的基本操作”~ 想要了解更多&#x1f236;️MYSQL 数据库的命令行总结&#xff01;&#xff01;&#xff01; “我是你的敌人,…

python 配置 oracle instant client

1.问题描述 想用python连接oracle数据库&#xff0c;百度得知需要cx_Oracle这个第三方库 import cx_Oracle# 设置Oracle数据源名称 dsn cx_Oracle.makedsn(host, port, service_nameservice_name)# 创建数据库连接 connection cx_Oracle.connect(userusername, passwordpas…

使用FastGPT制做一个AI网站日志分析器

越来越的多网站面临每天上千次的扫描和各类攻击&#xff0c;及时发现攻击IP&#xff0c;并有效的屏蔽不良访问成为网站安全的重要保障&#xff0c;这里我们使用AI来完成对网站日志的日常分析。 我们来使用FastGPT来制做一个AI网站日志析器&#xff0c;下面就开始&#xff1a; …

RabbitMQ中的Work Queues模式

在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;是实现异步通信和解耦系统的关键组件之一。RabbitMQ 是一个广泛使用的开源消息代理软件&#xff0c;支持多种消息传递模式。其中&#xff0c;Work Queues&#xff08;工作队列&#xff09;模式是一…

【Python爬虫系列】_032.Scrapy_全站爬取

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈

音频声音太小怎么调大?调大音频声音的几种方法

音频声音太小怎么调大&#xff1f;音频声音过小可能由多种原因引起。从设备本身的硬件设置&#xff0c;到应用程序或播放软件的音量控制&#xff0c;再到文件本身的音频质量&#xff0c;都可能是导致声音过小的因素。尤其是在观看视频或听音乐时&#xff0c;若音量过低&#xf…

条件随机场(CRF)详解:原理、算法与实现(深入浅出)

目录 1. 引言2. 什么是条件随机场&#xff1f;2.1 直观理解2.2 形式化定义 3. CRF的核心要素3.1 特征函数3.2 参数学习 4. 实战案例&#xff1a;命名实体识别5. CRF vs HMM6. CRF的优化与改进6.1 特征选择6.2 正则化 7. 总结与展望参考资料 1. 引言 条件随机场(Conditional Ra…