当前位置: 首页 > article >正文

04商品详情

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 详情页-热榜区域
    • 在Detail文件夹中新建组件
    • 接口
    • 导入调用
    • 渲染模版
  • 适配不同标题title
  • 图片预览
    • sku组件(商品的一些规格)
    • 把components文件中的组件注册为全局组件,方便共享
  • 总结


详情页-热榜区域

在这里插入图片描述

在Detail文件夹中新建组件

src\views\Detail\components\DetailHot.vue

<template>
  <div class="goods-hot">
    <h3>周日榜单</h3>
    <!-- 商品区块 -->
    <RouterLink to="/" class="goods-item" v-for="item in 3" :key="item.id">
        <img :src="item.picture" alt="" />
        <p class="name ellipsis">一双鞋</p>
        <p class="desc ellipsis">还有有一双</p>
        <p class="price">¥200.00</p>
    </RouterLink>
  </div>
</template>

<script setup>

</script>

<style scoped lang="scss">
.goods-hot{
    h3{
        height: 70px;
        background: $helpColor;
        color: #fff;
        font-size: 18px;
        line-height: 70px;
        padding-left: 25px;
        margin-bottom: 10px;
        font-weight: normal;
    }
    .goods-item{
        display: block;
        padding: 20px 30px;
        text-align: center;
        background: #fff;
        img{
            width: 160px;

        }
        p{
            padding-top: 10px;
        }
        .name{
            font-size: 16px;
        }
        .desc{
            color: #999;
            height: 29px;
        }
        .price{
            color: $priceColor;
            font-size: 20px;
        }
    }
}

</style>

接口

src\apis\category.js

/**
 * 获取热榜商品
 * @param {Number} id - 商品id
 * @param {Number} type - 1代表24小时热销榜 2代表周热销榜
 * @param {Number} limit - 获取个数
 */
 export const fetchHotGoodsAPI = ({id,type,limit=3})={
 return request({
  	url:'/goods/hot',
  	id,
  	type,
  	limit
  })
 }

导入调用

src\views\Detail\components\DetailHot.vue

<script>
	import {ref} from 'vue'
	import {getHotGoodsAPI} from '@/apis/category'
	import {useRoute}from 'vue-router'
	const goodList = ref([])
	const route = useRoute()
	const getHotList = async()=>{
	 const res = await getHotGoodsAPI({
	   id:route.params.id,
	   type:1 
	 }) 
	 goodList.value = res.data.result
	}
	getHotList()
</script>

渲染模版

 <!-- 商品区块 -->
    <RouterLink to="/" class="goods-item" v-for="item in items" :key="item.id">
        <img :src="item.picture" alt="" />
        <p class="name ellipsis">{{item.name}}</p>
        <p class="desc ellipsis">{{item.desc}}</p>
        <p class="price">¥{{item.price}}</p>
    </RouterLink>

适配不同标题title

src\views\Detail\components\DetailHot.vue

// hotType字段 1代表24小时热销榜 2代表周热销榜
const props = defineProps({
    hotType:{
        type:Number
    }
})
const TITLEMAP = {
    1:"24小时热销榜",
    2:"周热销榜"
}
const title = computed(()=>{
   return TITLEMAP[props.hotType]
})

图片预览

src\components\PicturePreview.vue

<template>
  <div class="goods-image">
    <!-- 左侧大图 -->
    <div class="middle" ref="target">
      <img :src="imageList[activeIndex]" alt="" />
      <!-- 蒙层小滑块 -->
      <div class="layer" v-show="!isOutside" :style="{left:`${left}px`,top:`${top}px`}"></div>
    </div>
    <!-- 小图列表 -->
    <ul class="small">
      <li v-for="(img,i) in imageList" :key="i" @mouseenter="enterhandler(i)" :class="{active:i === activeIndex}">
        <img :src="img" alt="">
      </li>
    </ul>
    <!-- 放大镜大图 -->
    <div class="large" :style="[{
      backgroundImage: `url(${imageList[activeIndex]})`,
        backgroundPositionX: `${positionX}px`,
        backgroundPositionY: `${positionY}px`,
    }]" v-show="!isOutside">

    </div>
  </div>
</template>

<script setup>
import {ref,watch } from 'vue'
import { useMouseInElement } from '@vueuse/core';
//通过props接收数据
defineProps({
  imageList:{
    type:Array,
    default:()=>[]
  }
})
const imageList = [
  "https://yanxuan-item.nosdn.127.net/d917c92e663c5ed0bb577c7ded73e4ec.png",
  "https://yanxuan-item.nosdn.127.net/e801b9572f0b0c02a52952b01adab967.jpg",
  "https://yanxuan-item.nosdn.127.net/b52c447ad472d51adbdde1a83f550ac2.jpg",
  "https://yanxuan-item.nosdn.127.net/f93243224dc37674dfca5874fe089c60.jpg",
  "https://yanxuan-item.nosdn.127.net/f881cfe7de9a576aaeea6ee0d1d24823.jpg"
]
// 实现鼠标移入交互
const curIndex = ref(0);
const mouseEnterFn = (i)=>curIndex.value = i;
// 小图切换大图显示
const activeIndex = ref(0)
const enterhandler = (i)=>{
  activeIndex.value = i
}
// 获取鼠标相对位置
const target = ref(null)
const {elementX,elementY,isOutside} = useMouseInElement(target)
// 控制滑块跟随鼠标移动
const left = ref(0)
const top =ref(0)

const positionX = ref(0)
const positionY =ref(0)
watch([elementX,elementY,isOutside],()=>{
  console.log('xy变化了');
  if(isOutside.value) return //
  console.log('鼠标移入了盒子')
  // 有效范围内控制滑块距离
  // 横向
  if(elementX.value>100 && elementX.value <300){
    left.value = elementX.value -100
  }
  // 纵向
  if(elementY.value > 100 && elementY.value < 300){
    top.value = elementY.value - 100
  }
  // 处理边界
  if(elementX.value > 300){
    left.value = 200
  }
  if(elementX.value <100){
    left.value = 0
  }
  // 控制大图的显示
  positionX.value = -left.value * 2
  positionY.value = -top.value * 2
 
})
</script>

<style scoped lang="scss">
 .goods-image{
  width: 480px;
  height: 400px;
  position: relative;
  display: flex;
  .middle{
    width: 400px;
    height: 400px;
    background: #f5f5f5;
    img{
      width: 100%;
    }
  }
  .large{
    position: absolute;
    top: 0;
    left: 412px;
    width: 400px;
    height: 400px;
    z-index: 500;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
    background-repeat: no-repeat;
    background-size: 800px 800px;
    background-color: #f8f8f8;
  }
  .layer{
    width: 200px;
    height: 200px;
    background:rgba(0,0,0,0.2);
    left: 0;
    top: 0;
    position: absolute;
  }
  .small{
    width: 80px;
      li{
      width: 68px;
      height: 68px;
      margin-left: 12px;
      margin-bottom: 15px;
      cursor: pointer;
      list-style: none;
      img{
        width: 100%;
      }
      &:hover{
        border: 2px solid $xtxColor;
      }
    }
    .active{
        border: 2px solid $xtxColor;
    }
  }
 }
</style>

sku组件(商品的一些规格)

在这里插入图片描述

<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. 过滤出有库存有效的sku
      if (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之后组合成key
        const 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) {
        // 从路径字典中得到skuId
        const 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>

src\views\Detail\index.vue组件中添加sku组件

 <!-- sku组件 -->
    <XtxSku :goods="goods"/>
 <!-- 数据组件 -->

把components文件中的组件注册为全局组件,方便共享

src\components\index.js

// 全局组件统一插件化 进行全局化注册
import ImageView from './ImageView/PicturePreview.vue'
import Sku from './XtxSku/index.vue'
export const componentPlugin = {
  install(app){
    app.component('XtxImageView',ImageView)
    app.component('XtxSku',Sku) 
  }
}

src\main.js

import {componentPlugin} from '@/components'
app.use(componentPlugin)

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。


http://www.kler.cn/a/511967.html

相关文章:

  • 2024春秋杯密码题第一、二天WP
  • CamemBERT:一款出色的法语语言模型
  • 移远通信多模卫星通信模组BG95-S5获得Skylo网络认证,进一步拓展全球卫星物联网市场
  • 农业农村大数据应用场景|珈和科技“数字乡村一张图”解决方案
  • mfc操作json示例
  • 【前端动效】HTML + CSS 实现打字机效果
  • react中,使用antd的Upload组件切片上传.zip文件及压缩包的下载
  • PyTorch使用教程(15)-常用开源数据集简介
  • 【STM32-学习笔记-15-】MAX7219点阵屏模块
  • Redis - General - 未授权访问漏洞(用户配置问题)
  • Quartus:开发使用及 Tips 总结
  • 适合快递驿站出库仪一体机的安卓主板
  • 如何在龙蜥 OS(AliOS)上安装极狐GitLab?
  • canvas snake game
  • 面向CTF的python_requests库的学习笔记
  • 二十项零信任相关的前沿和趋势性技术-Extranet as a Service
  • 中国综合算力指数(2024年)报告汇总PDF洞察(附原数据表)
  • C#集合排序指南:掌握自定义排序的多种方法
  • 汇编学习笔记(自用)
  • LLM(3) : 浏览器录制16K的音频并上传到后端
  • UG NX二次开发(C#)-创建三维直线段并倒圆
  • 研1如何准备才能找到大厂实习?
  • Sudo命令的配置及使用
  • 【前端】CSS学习笔记(1)
  • Unity自学之旅01
  • JupyterLab 安装以及部分相关配置