黑马程序员前端 Vue3 小兔鲜电商项目——(五)一级分类页

image-20230622214952268

路由配置

配置路由

一级分类 URL:http://127.0.0.1:5173/categopry/1005000,一级分类页是通过 /category/categoryId 的方式控制跳转,所以我们需要让路由接收 id 参数,修改 src\router\index.js 文件:

routes: [
  {
    
    
    path: '/',
    component: Layout,
    children: [
      {
    
    
        path: '',
        component: Home,
      },
      {
    
    
        path: 'category/:id', // :id 动态接收分类id参数
        component: Category,
      }
    ]
  }
]

修改导航区域链接

<RouterLink :to="`/category/${item.id}`">{
   
   { item.name }}</RouterLink>

面包屑导航渲染

模板代码

修改 src\views\Category\index.vue 文件,添加以下模板代码:

<script setup>

</script>

<template>
  <div class="top-category">
    <div class="container m-top-20">
      <!-- 面包屑 -->
      <div class="bread-container">
        <el-breadcrumb separator=">">
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
          <el-breadcrumb-item>居家</el-breadcrumb-item>
        </el-breadcrumb>
      </div>
    </div>
  </div>
</template>


<style scoped lang="scss">
.top-category {
  h3 {
    font-size: 28px;
    color: #666;
    font-weight: normal;
    text-align: center;
    line-height: 100px;
  }

  .sub-list {
    margin-top: 20px;
    background-color: #fff;

    ul {
      display: flex;
      padding: 0 32px;
      flex-wrap: wrap;

      li {
        width: 168px;
        height: 160px;


        a {
          text-align: center;
          display: block;
          font-size: 16px;

          img {
            width: 100px;
            height: 100px;
          }

          p {
            line-height: 40px;
          }

          &:hover {
            color: $xtxColor;
          }
        }
      }
    }
  }

  .ref-goods {
    background-color: #fff;
    margin-top: 20px;
    position: relative;

    .head {
      .xtx-more {
        position: absolute;
        top: 20px;
        right: 20px;
      }

      .tag {
        text-align: center;
        color: #999;
        font-size: 20px;
        position: relative;
        top: -20px;
      }
    }

    .body {
      display: flex;
      justify-content: space-around;
      padding: 0 40px 30px;
    }
  }

  .bread-container {
    padding: 25px 0;
  }
}
</style>

封装接口

获取二级分类页表,

import http from '@/utils/http'

/**
 * @description: 获取分类数据 /category?id=10020
 * @param {*} id 分类id 
 * @return {*}
 */
export const getCategoryAPI = (id) => {
    
    
    return http({
    
    
        url: '/category',
        params: {
    
    
            id
        }
    })
}

渲染面包屑导航

image-20230622183941751

通过获取路由中传递的参数(分类 Id)获取分类数据,渲染到导航:

<script setup>
import {ref,onMounted} from 'vue'
import {getCategoryAPI} from '@/apis/category'
import {useRoute} from 'vue-router'

const categoryData = ref({})
const route = useRoute()
const getCategoryData = async(id)=>{
   const res = await getCategoryAPI(id)
   console.log(res);
   categoryData.value = res.result
}

onMounted(()=>{
   // 获取路由参数 id, useRoute() -> route 等价于this.$route
   getCategoryData(route.params.id)
})

</script>

<template>
  <div class="top-category">
    <div class="container m-top-20">
      <!-- 面包屑 -->
      <div class="bread-container">
        <el-breadcrumb separator=">">
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
          <el-breadcrumb-item>{
   
   { categoryData.name }}</el-breadcrumb-item>
        </el-breadcrumb>
      </div>
    </div>
  </div>
</template>

分类Banner渲染

分类轮播图和首页轮播图的区别只有一个,接口参数不同,其余逻辑完成一致。

获取 Banner 数据的接口参数如下,我们可以通过传递不同的参数来区分分类轮播图和首页轮播图:

参数名 位置 类型 必填 说明
distributionSite Query String 广告区域展示位置(投放位置 投放位置,1为首页,2为分类商品页) 默认是1

修改接口

我们需要对获取首页轮播图的接口进行修改,添加传递参数,让其适用于获取分类轮播图:

export function getBannerAPI(params={
     
     }) {
    
    
    // 默认为1 商品为2
    const {
    
    distributionSite='1'} = params
    return http({
    
    
        url: 'home/banner',
        params:{
    
    
            distributionSite
        }
    })
}

上述代码中,该语句是使用 ES6(ECMAScript 2015)中的解构赋值语法:

const {
    
    distributionSite='1'} = params

解构赋值的基本语法是使用花括号 {} 来表示一个对象,或使用方括号 [] 来表示一个数组,然后在里面使用变量名或数组下标等形式来提取值。在变量名后面使用等号 = 来设置默认值。

这行代码的意思是,如果调用该函数时不传递任何参数,distributionSite 默认值就会是字符串'1'。如果传递了params对象,但是params对象中没有distributionSite属性,也会将distributionSite默认值设为字符串'1'

渲染 Banner 模块

在 src\views\Category\index.vue 文件中,获取分类 Banner 数据,并添加轮播图模块:

<script setup>
import {ref,onMounted} from 'vue'
import {getCategoryAPI} from '@/apis/category'
import { getBannerAPI } from "@/apis/home";
import {useRoute} from 'vue-router'

const categoryData = ref({})
const route = useRoute()
const getCategoryData = async(id)=>{
   const res = await getCategoryAPI(id)
   console.log(res);
   categoryData.value = res.result
}

onMounted(()=>{
   // 获取路由参数 id, useRoute() -> route 等价于this.$route
   getCategoryData(route.params.id)
})

const bannerList = ref([])

const getBanner = async () => {
   const res = await getBannerAPI({
      distributionSite:'2'
   })
   console.log(res)
   bannerList.value = res.result
}

onMounted(() => {
    getBanner()
})
</script>

<template>
  <div class="top-category">
    <div class="container m-top-20">
      <!-- 面包屑 -->
      <div class="bread-container">
        <el-breadcrumb separator=">">
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
          <el-breadcrumb-item>{
   
   { categoryData.name }}</el-breadcrumb-item>
        </el-breadcrumb>
      </div>
      <!-- Banner 轮播图 -->
      <div class="home-banner">
        <el-carousel height="500px">
            <el-carousel-item v-for="item in bannerList" :key="item.id">
                <img :src="item.imgUrl" alt="">
            </el-carousel-item>
        </el-carousel>
    </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.top-category {
  h3 {
    font-size: 28px;
    color: #666;
    font-weight: normal;
    text-align: center;
    line-height: 100px;
  }

  .sub-list {
    margin-top: 20px;
    background-color: #fff;

    ul {
      display: flex;
      padding: 0 32px;
      flex-wrap: wrap;

      li {
        width: 168px;
        height: 160px;


        a {
          text-align: center;
          display: block;
          font-size: 16px;

          img {
            width: 100px;
            height: 100px;
          }

          p {
            line-height: 40px;
          }

          &:hover {
            color: $xtxColor;
          }
        }
      }
    }
  }

  .ref-goods {
    background-color: #fff;
    margin-top: 20px;
    position: relative;

    .head {
      .xtx-more {
        position: absolute;
        top: 20px;
        right: 20px;
      }

      .tag {
        text-align: center;
        color: #999;
        font-size: 20px;
        position: relative;
        top: -20px;
      }
    }

    .body {
      display: flex;
      justify-content: space-around;
      padding: 0 40px 30px;
    }
  }

  .bread-container {
    padding: 25px 0;
  }
}

.home-banner {
    width: 1240px;
    height: 500px;
    z-index: 98;

    img {
        width: 100%;
        height: 500px;
    }
}
</style>

导航激活设置分类列表渲染

当页面显示分类数据时,该分类在导航栏中的样式为激活样式:

image-20230622194603783

导航激活状态设置

RouterLink 组件默认支持激活样式显示的类名,只需要给 active-class 属性设置对应的类名即可。修改 src\views\Layout\components\LayoutHeader.vue 文件和 src\views\Layout\components\LayoutFixed.vue 文件:

<RouterLink active-class="active" :to="`/category/${item.id}`">{
   
   { item.name }}</RouterLink>

分类数据模版

修改 src\views\Category\index.vue 文件,在【Banner 轮播图】模块下添加分类数据模板:

<!-- 分类数据 -->
<div class="sub-list">
  <h3>全部分类</h3>
  <ul>
    <li v-for="i in categoryData.children" :key="i.id">
      <RouterLink to="/">
        <img :src="i.picture" />
        <p>{
   
   { i.name }}</p>
      </RouterLink>
    </li>
  </ul>
</div>
<div class="ref-goods" v-for="item in categoryData.children" :key="item.id">
  <div class="head">
    <h3>- {
   
   { item.name }}-</h3>
  </div>
  <div class="body">
    <GoodsItem v-for="good in item.goods" :good="good" :key="good.id" />
  </div>
</div>

在 script 中引入 GoodsItem 商品展示模块:

import GoodsItem from '../Home/components/GoodsItem.vue'

路由缓存问题

解决方案

在 Vue 中使用带有参数的路由时需要注意的是,当用户从 /users/johnny 导航到 /users/jolyne 时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。这也意味着组件的生命周期钩子不会被调用。

即我们在一级导航时出现的问题:一级分类的切换正好满足上面的条件,组件实例复用,导致分类数据无法更新。也就是当我们在【/category/1005000】居家页面时,点击【美食分类】页面的数据不会进行变化。

缓存问题:当路由 path 一样,参数不同的时候会选择直接复用路由对应的组件导致数据无法更新,解决方案:

  1. 让组件实例不复用,强制销毁重建

    给 router-view 添加 key,破坏缓存

  2. 监听路由变化,变化之后执行数据更新操作

    beforeRouteUpdate:钩子函数可以在每次路由更新之前执行,在回调中执行需要数据更新的业务逻辑即可

beforeRouteUpdate

在 src\views\Category\index.vue 文件中引入 onBeforeRouteUpdate 方法,通过给 getCategoryData 方法传递分类 Id 参数获取分类数据:

<script setup>
import {
    
     useRoute,onBeforeRouteUpdate } from 'vue-router'

const categoryData = ref({
    
    })
const route = useRoute()
const getCategoryData = async (id) => {
    
    
   const res = await getCategoryAPI(id)
   console.log(res);
   categoryData.value = res.result
}

//目标:路由参数变化的时候可以把分类数据接口重新发送
onBeforeRouteUpdate((to)=>{
    
    
   console.log("路由变化了");
   route.params.id 存在滞后性,无法及时获取路由参数
   //通过参数to目标路由对象获取路由参数
   console.log(to);
   getCategoryData(to.params.id)
})
</script>

基于业务逻辑的函数拆分

基于逻辑函数拆分业务是指把同一个组件中独立的业务代码通过函数做封装处理,提升代码的可维护性。基本思想:把组件内独立的业务逻辑通过 useXXX 函数做封装处理,在组件中做组合使用。

image.png

步骤如下:

  1. 按照业务声明以use打头的逻辑函数
  2. 把独立的业务逻辑封装到各个函数内部
  3. 函数内部把组件中需要用到的数据或者方法return出去
  4. 在组件中调用函数把数据或者方法组合回来使用

封装轮播图

封装 userBanner() 函数

新建 src\views\Category\composables\useBanner.js 文件,将 src\views\Category\index.vue 文件中关于 Banner 的部分剪切进来:

// 封装banner轮播图相关的业务代码
import {
    
     ref, onMounted } from 'vue'
import {
    
     getBannerAPI } from "@/apis/home"

export function useBanner(){
    
    
    const bannerList = ref([])

    const getBanner = async () => {
    
    
       const res = await getBannerAPI({
    
    
          distributionSite:'2'
       })
       console.log(res)
       bannerList.value = res.result
    }
    
    onMounted(() => {
    
    
       getBanner()
    })
    return {
    
    bannerList};
}

修改原代码

修改 src\views\Category\index.vue 文件,导入封装好的 userBanner() 函数,通过解构获取 bannerList:

import {
    
     useBanner } from '@/views/Category/composables/useBanner'

const {
    
     bannerList } = useBanner()

封装分类数据

封装 useCategory() 函数

新建 src\views\Category\composables\useCategory.js 文件,将 src\views\Category\index.vue 文件中关于 Banner 的部分剪切进来:

// 封装分类数据相关业务代码
import {
    
     ref, onMounted } from 'vue'
import {
    
     getCategoryAPI } from '@/apis/category'
import {
    
     useRoute, onBeforeRouteUpdate } from 'vue-router'

export function useCategory() {
    
    
    const categoryData = ref({
    
    })
    const route = useRoute()
    const getCategoryData = async (id) => {
    
    
        const res = await getCategoryAPI(id)
        console.log(res);
        categoryData.value = res.result
    }

    onMounted(() => {
    
    
        // 获取路由参数 id, useRoute() -> route 等价于this.$route
        getCategoryData(route.params.id)
    })

    //目标:路由参数变化的时候可以把分类数据接口重新发送
    onBeforeRouteUpdate((to) => {
    
    
        console.log("路由变化了");
        //route.params.id 存在滞后性,无法及时获取路由参数
        //通过参数to目标路由对象获取路由参数
        console.log(to);
        getCategoryData(to.params.id)
    })

    return {
    
     categoryData }
}

修改原代码

修改 src\views\Category\index.vue 文件,导入封装好的 useCategory() 函数,通过解构获取 categoryData:

import {
    
     useCategory } from '@/views/Category/composables/useCategory'

const {
    
     categoryData } = useCategory()

Guess you like

Origin blog.csdn.net/qq_20185737/article/details/131344714