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-describes
              v-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.data
    skuItemInfo.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,//选择的sku
    buyNumber: 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
  })
  //得出当前的skuId
  let 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计算那里,是这个详情页面唯一的难点,其他的部分我就不多说了。如果读者觉得有哪些部分不全,或者想要了解其他部分,随时评论。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

uniapp 微信小程序 功能入口

单行单独展示 效果图 html <view class"shopchoose flex jsb ac" click"routerTo(要跳转的页面)"><view class"flex ac"><image src"/static/dyd.png" mode"aspectFit" class"shopchooseimg"&g…

6.1 初探MapReduce

MapReduce是一种分布式计算框架&#xff0c;用于处理大规模数据集。其核心思想是“分而治之”&#xff0c;通过Map阶段将任务分解为多个简单任务并行处理&#xff0c;然后在Reduce阶段汇总结果。MapReduce编程模型包括Map和Reduce两个阶段&#xff0c;数据来源和结果存储通常在…

聚观早报 | 百度回应进军短剧;iPad Air将升级OLED

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 12月18日消息 百度回应进军短剧 iPad Air将升级OLED 三星Galax S25 Ultra配色细节 一加Ace 5系列存储规格 小米…

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB 是一款基于南京沁恒&#xff08;WCH&#xff09;推出的高性能、低功耗无线通信芯片CH582F的开发板。以下是该开发板的功能和参数详细介绍&#xff1a; 主要特性 双模蓝牙支持&#xff1a; 支持蓝牙5.0标准&#xff0…

【软件工程复习】

第1章 软件工程概述 1.2软件工程 ​ 1983年IEEE给出的定义&#xff1a;“软件工程是 开发、运行、维护和修复软件的系统方法 ” 1.4软件生存期 软件开发和运行维护由三个时期组成&#xff1a; 软件定义时期软件开发时期运行维护时期 里程碑指可以用来标识项目进程状态的事…

DuckDB: 从MySql导出数据至Parquet文件

在这篇文章中&#xff0c;介绍使用DuckDB将数据从MySQL数据库无缝传输到Parquet文件的过程。该方法比传统的基于pandas方法更高效、方便&#xff0c;我们可以从DuckDB cli实现&#xff0c;也可以结合Python编程方式实现&#xff0c;两者执行核心SQL及过程都一样。 Parquet格式…

safe area helper插件

概述 显示不同机型的必能显示的区域 实现步骤 引入safearea&#xff0c;引入其中的safearea的csharp 为cancas加入gameobject gameobject中加入safearea脚本 将UI作为这个gameobject的子物体&#xff0c;就可以完成显示

数据结构 ——二叉树转广义表

数据结构 ——二叉树转广义表 1、树转广义表 如下一棵树&#xff0c;转换为广义表 root(c(a()(b()()))(e(d()())(f()(j(h()())())))) (根&#xff08;左子树&#xff09;&#xff08;右子树&#xff09;) 代码实现 #include<stdio.h> #include<stdlib.h>//保存…

实现echart大屏动画效果及全屏布局错乱解决方式

如何实现echarts动画效果?如何实现表格或多个垂直布局的柱状图自动滚动效果?如何解决tooltip位置超出屏幕问题,如何解决legend文字过长,布局错乱问题?如何处理饼图的中心图片永远居中? 本文将主要解决以上问题,如有错漏,请指正. 一、大屏动画效果 这里的动画效果主要指&…

【YashanDB知识库】如何处理yasql输入交互模式下单行字符总量超过限制4000字节

现象 在yasql执行sql语句后报错&#xff1a;YASQL-00021 input line overflow (>4000 byte at line 4) 原因 yasql在交互模式模式下单行字符总量限制4000字节&#xff0c;超出该限制即报错。 交互式模式下&#xff0c;yasql会显示一个提示符&#xff0c;通常是 SQL>…

DALL·E 2(内含扩散模型介绍)-生成式模型【学习笔记】

视频链接&#xff1a;DALLE 2&#xff08;内含扩散模型介绍&#xff09;【论文精读】_哔哩哔哩_bilibili&#xff08;up主讲的非常好&#xff0c;通俗易懂&#xff0c;值得推荐&#xff09; 目录 1、GAN模型 2、VAE模型 2.1、AE&#xff08;Auto-Encoder&#xff09; 2.2、…

FPGA 16 ,Verilog中的位宽:深入理解与应用

目录 前言 一. 位宽的基本概念 二. 位宽的定义方法 1. 使用向量变量定义位宽 ① 向量类型及位宽指定 ② 位宽范围及位索引含义 ③ 存储数据与字节数据 2. 使用常量参数定义位宽 3. 使用宏定义位宽 4. 使用[:][-:]操作符定义位宽 1. 详细解释 : 操作符 -: 操作符 …

使用 Vue3 实现摄像头拍照功能

参考资料:MediaDevices.getUserMedia() - Web API | MDN 重要: navigator.mediaDevices.getUserMedia 需要在安全的上下文中运行。现代浏览器要求摄像头和麦克风的访问必须通过 HTTPS 或 localhost&#xff08;被视为安全的本地环境&#xff09;进行,如果上传服务器地址是http…

2024安装hexo和next并部署到github和服务器最新教程

碎碎念 本来打算写点算法题上文所说的题目&#xff0c;结果被其他事情吸引了注意力。其实我之前也有过其他博客网站&#xff0c;但因为长期不维护&#xff0c;导致数据丢失其实是我懒得备份。这个博客现在部署在GitHub Pages上&#xff0c;github不倒&#xff0c;网站不灭&…

RTMP推流平台EasyDSS在无人机推流直播安防监控中的创新应用

无人机与低空经济的关系密切&#xff0c;并且正在快速发展。2024年中国低空经济行业市场规模达到5800亿元&#xff0c;其中低空制造产业占整个低空经济产业的88%。预计未来五年复合增速将达到16.03%。 随着科技的飞速发展&#xff0c;公共安防关乎每一个市民的生命财产安全。在…

java全栈day16--Web后端实战(数据库)

一、数据库介绍 二、Mysql安装&#xff08;自行在网上找&#xff0c;教程简单&#xff09; 安装好了进行Mysql连接 连接语法&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroot -p密码 方法二&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroo…

基于注意力的几何感知的深度学习对接模型 GAABind - 评测

GAABind 作者是苏州大学的生物基础与医学院, 期刊是 Briefings in Bioinformatics, 2024, 25(1), 1–14。GAABind 是一个基于注意力的几何感知蛋白-小分子结合模式与亲和力预测模型,可以捕捉小分子和蛋白的几何、拓扑结构特征以及相互作用。使用 PDBBind2020 和 CASF2016 作…

【CSS in Depth 2 精译_080】 13.1:CSS 渐变效果(中)——不同色彩空间的颜色插值算法在 CSS 渐变中的应用

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 13 章 渐变、阴影与混合模式】 ✔️ 13.1 渐变 ✔️ 13.1.1 使用多个颜色节点&#xff08;上&#xff09;13.1.2 颜色插值方法&#xff08;中&#xff09; ✔️13.1…

虚拟机VirtualBox安装最新版本Oracle数据库

https://www.oracle.com/database/technologies/databaseappdev-vm.html 如上所示&#xff0c;从Oracle官方网站上下载最新版本的VirtualBox虚拟机对应的Oracle数据库安装源文件。 如上所示&#xff0c;在VirtualBox中导入下载的Oracle安装源文件。 如上所示&#xff0c;导入…

热更新解决方案4——xLua热补丁

概述 运行时不在执行C#中的代码&#xff0c;而是执行Lua中的代码&#xff0c;相当于是打了个补丁。 1.第一个热补丁 2.多函数替换 3.协程函数替换 在原HotfixMain脚本中只加个协程函数即可&#xff08;和在Start中启动协程函数&#xff09; 4.索引器和属性替换 在HotfixMain中…