【B站 heima】小兔鲜Vue3 项目学习笔记Day03

文章目录

  • Home
    • 1.Home整体结构搭建和分类实现
    • 2. banner轮播图功能
    • 3. Home 面板组件封装
    • 4.新鲜好物和人气推荐实现
    • 5. 图片懒加载指令实现
    • 6. Home- product产品列表实现
    • 7. Home-GoodsItem 组件封装
  • 一级路由
    • 1. 整体认识和路由配置
    • 2. 面包屑导航
    • 3. 一级分类 - 轮播图的实现
    • 4. 激活状态控制
    • 5.分类列表的渲染
    • 6.一级分类-解决路由缓存问题
    • 7. 使用 逻辑函数 拆分业务
    • 小结

Home

1.Home整体结构搭建和分类实现

在这里插入图片描述

Home文件夹创建components五个组件HomeBanner、HomeCategory、HomeHot、HomeNew、HomeProduct,写点代码。

<template>我是...
</template>

打开Home的index.vue,引入并使用组件

<script setup>
import HomeCategory from './components/HomeCategory.vue'
import HomeBanner from './components/HomeBanner.vue'
import HomeNew from './components/HomeNew.vue'
import HomeHot from './components/HomeHot.vue'
import homeProduct from './components/HomeProduct.vue'
</script><template><div class="container"><HomeCategory /><HomeBanner /></div><HomeNew /><HomeHot /><homeProduct />
</template>

效果:
在这里插入图片描述
分类的实现

  • 准备模板
<!--HomeCategory-->
<script setup></script><template><div class="home-category"><ul class="menu"><li v-for="item in 9" :key="item"><RouterLink to="/">居家</RouterLink><RouterLink v-for="i in 2" :key="i" to="/">南北干货</RouterLink><!-- 弹层layer位置 --><div class="layer"><h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4><ul><li v-for="i in 5" :key="i"><RouterLink to="/"><img alt="" /><div class="info"><p class="name ellipsis-2">男士外套</p><p class="desc ellipsis">男士外套,冬季必选</p><p class="price"><i>¥</i>200.00</p></div></RouterLink></li></ul></div></li></ul></div>
</template><style scoped lang='scss'>
.home-category {width: 250px;height: 500px;background: rgba(0, 0, 0, 0.8);position: relative;z-index: 99;.menu {li {padding-left: 40px;height: 55px;line-height: 55px;&:hover {background: $xtxColor;}a {margin-right: 4px;color: #fff;&:first-child {font-size: 16px;}}.layer {width: 990px;height: 500px;background: rgba(255, 255, 255, 0.8);position: absolute;left: 250px;top: 0;display: none;padding: 0 15px;h4 {font-size: 20px;font-weight: normal;line-height: 80px;small {font-size: 16px;color: #666;}}ul {display: flex;flex-wrap: wrap;li {width: 310px;height: 120px;margin-right: 15px;margin-bottom: 15px;border: 1px solid #eee;border-radius: 4px;background: #fff;&:nth-child(3n) {margin-right: 0;}a {display: flex;width: 100%;height: 100%;align-items: center;padding: 10px;&:hover {background: #e3f9f4;}img {width: 95px;height: 95px;}.info {padding-left: 10px;line-height: 24px;overflow: hidden;.name {font-size: 16px;color: #666;}.desc {color: #999;}.price {font-size: 22px;color: $priceColor;i {font-size: 16px;}}}}}}}// 关键样式  hover状态下的layer盒子变成block&:hover {.layer {display: block;}}}}
}
</style>
  • 使用pinia渲染出来
<script setup>
import { useCategoryStore } from "@/stores/category";
const categoryStore = useCategoryStore()   //实例有了
</script>

看一下数据结构
在这里插入图片描述

<!--将数据绑定给对应字段--><li v-for="item in categoryStore.categoryList" :key="item"><RouterLink to="/">{{ item.name }}</RouterLink><RouterLink v-for="i in item.children.slice(0, 2)" :key="i" to="/">{{ i.name }}</RouterLink><!-- 弹层layer位置 --><div class="layer"><h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4><ul><li v-for="i in item.goods" :key="i.id"><RouterLink to="/"><img alt="" :src="i.picture" /><div class="info"><p class="name ellipsis-2">{{ i.name }}</p><p class="desc ellipsis">{{ i.desc }}</p><p class="price"><i>¥</i>{{ i.price }}</p></div></RouterLink></li>                   </ul></div></li>

在这里插入图片描述

2. banner轮播图功能

  • 准备模板
<script setup></script><!-- HomeBanner.vue -->
<template><div class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in 4" :key="item"><img src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/6d202d8e-bb47-4f92-9523-f32ab65754f4.jpg"alt=""></el-carousel-item></el-carousel></div>
</template><style scoped lang='scss'>
.home-banner {width: 1240px;height: 500px;position: absolute;left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;}
}
</style>
  • 熟悉elementPlus相关组件
  • 获取接口数据

封装接口

//获取轮播图数据              @/apis/home.js
export function getBannerAPI(params) {return httpInstance({url: '/home/banner',params})
}

获取数据

<script setup>
import { getBannerAPI } from '@/apis/home.js'
import { onMounted } from 'vue'// 获取轮播图数据
const getBannerList = async () => {const res = await getBannerAPI()console.log(res)
}onMounted(() => getBannerList())
</script>

在这里插入图片描述

  • 渲染组件
<script setup>
import { getBannerAPI } from '@/apis/home.js'
import { onMounted } from 'vue'
import { ref } from 'vue'// 获取轮播图数据
const bannerList = ref([])   //存储轮播图数组
const getBannerList = async () => {const res = await getBannerAPI()// console.log(res)bannerList.value = res.result// console.log(bannerList)}onMounted(() => getBannerList())
</script><!-- HomeBanner.vue -->
<template><div class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in bannerList" :key="item"><img :src="item.imgUrl" alt=""></el-carousel-item></el-carousel></div>
</template>

效果:
在这里插入图片描述

3. Home 面板组件封装

组件封装能解决的问题:a. 复用问题 b. 业务维护问题

新鲜好物人气推荐 模块 结构非常相似,我们考虑进行组件封装,将组件进行复用
在这里插入图片描述
组件封装核心思路: 把可复用的结构只写一次,把可能发生变化的部分(比如说上图的 新鲜好物人气推荐文字就是不同点;图片下的文字也不同)抽象成组件参数(props/插槽)

实现步骤:

  • 准备静态模板 在Home文件夹的components创建一个HomePanel.vue
<!-- HomePanel -->
<script setup></script><template><div class="home-panel"><div class="container"><div class="head"><!-- 主标题和副标题 --><h3>新鲜好物<small>新鲜出炉 品质靠谱</small></h3></div><!-- 主体内容区域 --><div> 主体内容 </div></div></div>
</template><style scoped lang='scss'>
.home-panel {background-color: #fff;.head {padding: 40px 0;display: flex;align-items: flex-end;h3 {flex: 1;font-size: 32px;font-weight: normal;margin-left: 6px;height: 35px;line-height: 35px;small {font-size: 16px;color: #999;margin-left: 20px;}}}
}
</style>
  • 抽象可变的部分:

    • 主标题副标题是纯文本,可以抽象为prop传入
    <!-- HomePanel 相关代码-->
    <script setup>
    //定义props
    defineProps({title: { type: String },   //主标题subTitile: { type: String }  //负标题
    })
    </script><template><div class="home-panel"><div class="container"><div class="head"><!-- 主标题和副标题 --><h3>{{ title }}<small>{{ subTitile }}</small></h3></div><!-- 主体内容区域 --><div> 主体内容 </div></div></div>
    </template>
    
    • 主体内容是复杂模板,抽象成插槽传入
<!-- HomePanel 相关代码--><!-- 主体内容区域 --><!-- <div> 主体内容 </div> --><slot />

导入到入口组件Home/index.vue中,测试一下

<!-- Home/index.vue -->
<script setup>
import HomeCategory from './components/HomeCategory.vue'
import HomeBanner from './components/HomeBanner.vue'
import HomeNew from './components/HomeNew.vue'
import HomeHot from './components/HomeHot.vue'
import HomeProduct from './components/HomeProduct.vue'
import HomePanel from './components/HomePanel.vue'
</script><template><div class="container"><HomeCategory /><HomeBanner /></div><HomeNew /><HomeHot /><HomeProduct /><!-- 测试,插槽要成对出现 --><HomePanel title="新鲜好物" subTitle="新鲜好物 好多商品"><div>我是新鲜好物的插槽内容</div></HomePanel><HomePanel title="人气推荐" subTitle="人气推荐 好多商品"><div>我是人气推荐的插槽内容</div></HomePanel>
</template>

在这里插入图片描述

纯展示类组件通用封装思路总结:

- 搭建纯静态的部分,不管可变的部分

- 抽象可变的部分为组件参数(非复杂的模板抽象为props,复杂的结构模板抽象为插槽)

4.新鲜好物和人气推荐实现

在这里插入图片描述

步骤:

  • 准备模板
<!-- 新鲜好物 HomeNew.vue -->
<script setup></script><template><div></div><!-- 下面是插槽主体内容模版
<ul class="goods-list"><li v-for="item in newList" :key="item.id"><RouterLink to="/"><img :src="item.picture" alt="" /><p class="name">{{ item.name }}</p><p class="price">&yen;{{ item.price }}</p></RouterLink></li>
</ul>
-->
</template><style scoped lang='scss'>
.goods-list {display: flex;justify-content: space-between;height: 406px;li {width: 306px;height: 406px;background: #f0f9f4;transition: all .5s;&:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 306px;height: 306px;}p {font-size: 22px;padding-top: 12px;text-align: center;text-overflow: ellipsis;overflow: hidden;white-space: nowrap;}.price {color: $priceColor;}}
}
</style>
  • 定制props

引入HomePanel,写入props

<!-- 新鲜好物 HomeNew.vue -->
<script setup>
import HomePanel from './HomePanel.vue'</script><template><HomePanel title="新鲜好物" subTitle="新鲜出炉 品质靠谱"></HomePanel><div></div>...
  • 定制插槽内容(接口+渲染模板)
//获取新鲜好物内容
export function getFreshAndGoodAPI(params) {return httpInstance({url: '/home/new',params})
}
<!-- 新鲜好物 HomeNew.vue 相关代码 -->
<script setup>
import HomePanel from './HomePanel.vue'
import { getFreshAndGoodAPI } from '@/apis/home.js'
import { onMounted } from 'vue'
import { ref } from 'vue'const freshAndGoodList = ref([])  //装新鲜好物数据的数组
const getFreshAndGood = async () => {const res = await getFreshAndGoodAPI()// console.log(res)freshAndGoodList.value = res.result
}onMounted(() => {getFreshAndGood()
})
</script><template><HomePanel title="新鲜好物" subTitle="新鲜出炉 品质靠谱"><div><ul class="goods-list"><li v-for="item in freshAndGoodList" :key="item.id"><RouterLink to="/"><img :src="item.picture" alt="" /><p class="name">{{ item.name }}</p><p class="price">&yen;{{ item.price }}</p></RouterLink></li></ul></div></HomePanel>
</template>

在这里插入图片描述
人气推荐 类似写法,在此贴上相关代码

//获取 人气推荐 内容
export function getHotAPI() {return httpInstance({url: '/home/hot'})
}
<script setup>
import HomePanel from './HomePanel.vue'
import { getHotAPI } from '@/apis/home'
import { ref } from 'vue'
const hotList = ref([])
const getHotList = async () => {const res = await getHotAPI()hotList.value = res.result
}
getHotList()</script><template><HomePanel title="人气推荐" sub-title="人气爆款 不容错过"><ul class="goods-list"><li v-for="item in hotList" :key="item.id"><RouterLink to="/"><img :src="item.picture" alt=""><p class="name">{{ item.title }}</p><p class="desc">{{ item.alt }}</p></RouterLink></li></ul></HomePanel>
</template><style scoped lang='scss'>
.goods-list {display: flex;justify-content: space-between;height: 426px;li {width: 306px;height: 406px;transition: all .5s;&:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 306px;height: 306px;}p {font-size: 22px;padding-top: 12px;text-align: center;}.desc {color: #999;font-size: 18px;}}
}
</style>

在这里插入图片描述

5. 图片懒加载指令实现

进入视口区域才发送图片请求。

步骤:

  • 熟悉指令语法

自定义指令:https://cn.vuejs.org/guide/reusability/custom-directives.html,记得选 组合式
在这里插入图片描述
定义指令

//定义全局指令         main.js
app.directive('img-lazy', {mounted(el, binding) {//el:指令绑定的那个元素 img//binding:binding》value 指令等于号后面绑定的表达式的值 图片urlconsole.log(el, binding.value)}
})
  • 判断图片是否进入视口(vueUse

使用useIntersectionObserver

//main.js 
//定义全局指令
app.directive('img-lazy', {mounted(el, binding) {//el:指令绑定的那个元素 img//binding:binding》value 指令等于号后面绑定的表达式的值 图片urlconsole.log(el, binding.value)useIntersectionObserver(el,  //监听的元素el([{ isIntersecting }]) => {   //isIntersecting 布尔值console.log(isIntersecting)  //图片进入视口区域,那么isIntersecting为true,否则false})}
})
  • 如果图片进入视口,发送图片资源请求(img.src = url
//定义全局指令
app.directive('img-lazy', {mounted(el, binding) {//el:指令绑定的那个元素 img//binding:binding》value 指令等于号后面绑定的表达式的值 图片url//console.log(el, binding.value)useIntersectionObserver(el,  //监听的元素el([{ isIntersecting }]) => {   //isIntersecting 布尔值//console.log(isIntersecting)  //图片进入视口区域,那么isIntersecting为true,否则falseif (isIntersecting) {//进入视口区域el.src = binding.value}})}
})
<!--HomeHot.vue-->
<img v-img-lazy="item.picture" alt="">

懒加载指令优化

  • a. 书写位置不合理。我们将懒加载指令封装为一个插件main.js入口文件只需要负责注册插件即可
//directinve新建index.js//定义懒加载插件
import { useIntersectionObserver } from "@vueuse/core";export const lazyPlugin = {install(app) {//懒加载指令逻辑app.directive('img-lazy', {mounted(el, binding) {//el:指令绑定的那个元素 img//binding:binding》value 指令等于号后面绑定的表达式的值 图片urlconsole.log(el, binding.value)useIntersectionObserver(el,  //监听的元素el([{ isIntersecting }]) => {   //isIntersecting 布尔值console.log(isIntersecting)  //图片进入视口区域,那么isIntersecting为true,否则falseif (isIntersecting) {//进入视口区域el.src = binding.value}})}})}
}

在main.js中注册插件

import { lazyPlugin } from './directives'app.use(lazyPlugin)
  • b. useIntersectionObserver对应元素的监听一直存在,除非手动停止监听,存在内存浪费

图片加载完成就不需要监听了,也就是完成第一次加载就停止监听

const { stop } = useIntersectionObserver(el,  //监听的元素el([{ isIntersecting }]) => {   //isIntersecting 布尔值//console.log(isIntersecting)  //图片进入视口区域,那么isIntersecting为true,否则falseif (isIntersecting) {//进入视口区域el.src = binding.valuestop()   //结构出的停止监听的方法}})

6. Home- product产品列表实现

重复几个下图结构组成

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 准备静态模板

    <!--HomeProduct.vue-->
    <script setup>
    import HomePanel from './HomePanel.vue'</script><template><div class="home-product"><!-- <HomePanel :title="cate.name" v-for="cate in goodsProduct" :key="cate.id"><div class="box"><RouterLink class="cover" to="/"><img :src="cate.picture" /><strong class="label"><span>{{ cate.name }}馆</span><span>{{ cate.saleInfo }}</span></strong></RouterLink><ul class="goods-list"><li v-for="good in cate.goods" :key="good.id"><RouterLink to="/" class="goods-item"><img :src="good.picture" alt="" /><p class="name ellipsis">{{ good.name }}</p><p class="desc ellipsis">{{ good.desc }}</p><p class="price">&yen;{{ good.price }}</p></RouterLink></li></ul></div></HomePanel> --></div>
    </template><style scoped lang='scss'>
    .home-product {background: #fff;margin-top: 20px;.sub {margin-bottom: 2px;a {padding: 2px 12px;font-size: 16px;border-radius: 4px;&:hover {background: $xtxColor;color: #fff;}&:last-child {margin-right: 80px;}}}.box {display: flex;.cover {width: 240px;height: 610px;margin-right: 10px;position: relative;img {width: 100%;height: 100%;}.label {width: 188px;height: 66px;display: flex;font-size: 18px;color: #fff;line-height: 66px;font-weight: normal;position: absolute;left: 0;top: 50%;transform: translate3d(0, -50%, 0);span {text-align: center;&:first-child {width: 76px;background: rgba(0, 0, 0, 0.9);}&:last-child {flex: 1;background: rgba(0, 0, 0, 0.7);}}}}.goods-list {width: 990px;display: flex;flex-wrap: wrap;li {width: 240px;height: 300px;margin-right: 10px;margin-bottom: 10px;&:nth-last-child(-n + 4) {margin-bottom: 0;}&:nth-child(4n) {margin-right: 0;}}}.goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all .5s;&:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}}}
    }
    </style>封装接口
    
  • 封装接口

/***获取商品模块Product*/
export const getGoodsAPI = () => {return httpInstance({url: '/home/goods'})
}
  • 获取数据渲染模板
<!--HomeProduct.vue-->
<script setup>
import HomePanel from './HomePanel.vue'
import { getGoodsAPI } from '@/apis/home.js'
import { ref, onMounted } from 'vue'const goodsProduct = ref([])
const getGoods = async () => {const res = await getGoodsAPI()goodsProduct.value = res.result
}
onMounted(() => {getGoods()
})
</script>

在这里插入图片描述

<!--HomeProduct.vue-->
<script setup>
import HomePanel from './HomePanel.vue'
import { getGoodsAPI } from '@/apis/home.js'
import { ref, onMounted } from 'vue'const goodsProduct = ref([])
const getGoods = async () => {const res = await getGoodsAPI()goodsProduct.value = res.result
}
onMounted(() => {getGoods()
})
</script><template><div class="home-product"><HomePanel :title="cate.name" v-for="cate in goodsProduct" :key="cate.id"><div class="box"><RouterLink class="cover" to="/"><img :src="cate.picture" /><strong class="label"><span>{{ cate.name }}馆</span><span>{{ cate.saleInfo }}</span></strong></RouterLink><ul class="goods-list"><li v-for="good in cate.goods" :key="good.id"><RouterLink to="/" class="goods-item"><img :src="good.picture" alt="" /><p class="name ellipsis">{{ good.name }}</p><p class="desc ellipsis">{{ good.desc }}</p><p class="price">&yen;{{ good.price }}</p></RouterLink></li></ul></div></HomePanel></div>
</template><style scoped lang='scss'>
.home-product {background: #fff;margin-top: 20px;.sub {margin-bottom: 2px;a {padding: 2px 12px;font-size: 16px;border-radius: 4px;&:hover {background: $xtxColor;color: #fff;}&:last-child {margin-right: 80px;}}}.box {display: flex;.cover {width: 240px;height: 610px;margin-right: 10px;position: relative;img {width: 100%;height: 100%;}.label {width: 188px;height: 66px;display: flex;font-size: 18px;color: #fff;line-height: 66px;font-weight: normal;position: absolute;left: 0;top: 50%;transform: translate3d(0, -50%, 0);span {text-align: center;&:first-child {width: 76px;background: rgba(0, 0, 0, 0.9);}&:last-child {flex: 1;background: rgba(0, 0, 0, 0.7);}}}}.goods-list {width: 990px;display: flex;flex-wrap: wrap;li {width: 240px;height: 300px;margin-right: 10px;margin-bottom: 10px;&:nth-last-child(-n + 4) {margin-bottom: 0;}&:nth-child(4n) {margin-right: 0;}}}.goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all .5s;&:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}}}
}
</style>

在这里插入图片描述

  • 图片懒加载

在这里插入图片描述

查看是否成功:向下滚动看看是否发送请求获取图片:
在这里插入图片描述

7. Home-GoodsItem 组件封装

结构类似,没必要重复定义,封装起来,方便复用
在这里插入图片描述
核心思想:将要显示的数据对象设计为props参数,传入什么对象,显示什么数据

新增GoodItem.vue, 将代码抽离出来。定义props。把相关样式抽离出来。

<script setup>
defineProps({goods: {type: Object,default: () => { }}
})
</script><template><RouterLink to="/" class="goods-item"><img v-img-lazy="goods.picture" alt="" /><p class="name ellipsis">{{ goods.name }}</p><p class="desc ellipsis">{{ goods.desc }}</p><p class="price">&yen;{{ goods.price }}</p></RouterLink>
</template>
<style scoped lang="scss">
.goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all .5s;&:hover {transform: translate3d(0, -3px, 0);box-shadow: 0 3px 8px rgb(0 0 0 / 20%);}img {width: 160px;height: 160px;}p {padding-top: 10px;}.name {font-size: 16px;}.desc {color: #999;height: 29px;}.price {color: $priceColor;font-size: 20px;}
}
</style>

使用:

<!--HomeProduct.vue-->
<ul class="goods-list"><li v-for="goods in cate.goods" :key="goods.id"><GoodItem :goods="goods" /></li></ul>

一级路由

1. 整体认识和路由配置

配置路由,点击哪个就跳转到哪个页面,地址还要有参数
在这里插入图片描述
找到LayoutHeader组件,拼接字符串
在这里插入图片描述
效果,鼠标放到category上,看左下角:
在这里插入图片描述
吸顶也要配置一下,一样的方式,打开LayoutFixed

 <!-- 导航区域 --><ul class="app-header-nav"><li class="home" v-for="item in categoryStore.categoryList" :key="item.id"><RouterLink :to="`/category/${item.id}`">{{ item.name }}</RouterLink></li></ul>

2. 面包屑导航

在这里插入图片描述

  • 准备模板
<!--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>
  • 封装接口 这个接口有一个参数,和之前的不一样
//  apis/category.js
//获取二级分类列表
export function getCategoryAPI(id) {return httpInstance({url: '/category',params: {id}})
}
  • 调用接口获取数据
<script setup>
import { getCategoryAPI } from '@/apis/category.js'
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'const categoryData = ref({})
const getCategoryData = anysc() => {const route = useRoute()const res = await getCategoryAPI(route.params.id)categoryData.value = res.result
}
onMounted(() => {getCategoryData()
})
</script>
  • 渲染模板
<!--Category/index.vue--> 
<el-breadcrumb-item>{{ categoryData.name }}</el-breadcrumb-item>

在这里插入图片描述

3. 一级分类 - 轮播图的实现

轮播图的结构很相似,我们改造一下接口,再复用一下首页轮播图的逻辑

  • 改造之前的接口(适配参数)
//获取轮播图banner数据
export function getBannerAPI(params = {}) {// 默认为1 商品为2const { distributionSite = '1' } = paramsreturn httpInstance({url: '/home/banner',params: {distributionSite}})
}
  • 迁移首页轮播图的逻辑,样式也要记得迁移
<script setup>
import { getCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'//获取数据
const categoryData = ref({})
const route = useRoute()
const getCategoryData = async () => {const res = await getCategoryAPI(route.params.id)categoryData.value = res.result
}// 获取轮播图数据
const bannerList = ref([])   //存储轮播图数组
const getBannerList = async () => {const res = await getBannerAPI({// 默认为1 商品为2distributionSite: '2'})// console.log(res)bannerList.value = res.result// console.log(bannerList)}onMounted(() => {getCategoryData()getBannerList()
})</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 class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in bannerList" :key="item"><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;// position: absolute;margin: 0 auto; //居中left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;}
}
</style>

4. 激活状态控制

点击后 样式发生如下改变
在这里插入图片描述
添加active-class属性

<!--LayoutHeader.vue--><ul class="app-header-nav"><li class="home" v-for="item in categoryStore.categoryList" :key="item.id"><RouterLink active-class="active" :to="`/category/${item.id}`">{{ item.name }}</RouterLink></li></ul>

在这里插入图片描述

5.分类列表的渲染

  • 模板

    <!--category/index.vue--><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" :goods="good" :key="good.id" /></div>
    </div>
    

    全部代码:

    <!--Category/index.vue-->
    <script setup>
    import { getCategoryAPI } from '@/apis/category.js'
    import { getBannerAPI } from '@/apis/home.js'
    import { ref, onMounted } from 'vue'
    import { useRoute } from 'vue-router'
    import GoodsItem from '../Home/components/GoodItem.vue'//获取数据
    const categoryData = ref({})
    const route = useRoute()
    const getCategoryData = async () => {const res = await getCategoryAPI(route.params.id)categoryData.value = res.result
    }// 获取轮播图数据
    const bannerList = ref([])   //存储轮播图数组
    const getBannerList = async () => {const res = await getBannerAPI({// 默认为1 商品为2distributionSite: '2'})// console.log(res)bannerList.value = res.result// console.log(bannerList)}onMounted(() => {getCategoryData()getBannerList()
    })</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 class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in bannerList" :key="item"><img :src="item.imgUrl" alt=""></el-carousel-item></el-carousel></div><!-- 分类列表 --><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="goods in item.goods" :goods="goods" :key="goods.id" /></div></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;// position: absolute;margin: 0 auto; //居中left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;}
    }
    </style>
    

6.一级分类-解决路由缓存问题

官方说明:
在这里插入图片描述
一级分类切换满足这个条件,组件实例复用,导致分类数据无法进行更新。

点击美食居家,参数会发生变化但是页面不会变化。

解决问题思路:

  • 办法1:不复用组件实例,强制销毁

以当前路由完整路径为key的值,给router-view组件绑定

<RouterView :key="$route.fullPath"/>

在这里插入图片描述

  • 办法2:监听路由变化,变化之后执行 数据更新 操作

使用beforeRouteUpdate导航钩子,这个函数可以再每次路由更新之前执行,在回调中执行需要数据更新的业务逻辑即可。

//	Category/index.vue
<script setup>
import { getCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import GoodsItem from '../Home/components/GoodItem.vue'
import { onBeforeRouteUpdate } from 'vue-router'
//获取数据
const categoryData = ref({})
const route = useRoute()
const getCategoryData = async () => {const res = await getCategoryAPI(route.params.id)categoryData.value = res.result
}// 获取轮播图数据
const bannerList = ref([])   //存储轮播图数组
const getBannerList = async (id = route.params.id) => {const res = await getBannerAPI(id)// console.log(res)bannerList.value = res.result// console.log(bannerList)}onMounted(() => {getCategoryData()getBannerList()
})//期望在路由参数变化时,分类数据接口重新发送
onBeforeRouteUpdate((to) => {getCategoryData(to.params.id)
})
</script>

在这里插入图片描述

7. 使用 逻辑函数 拆分业务

指的是,把同一个组件中独立的业务代码通过函数作封装处理,提高代码的可维护性

实现步骤:

  • 安装业务声明以use打头的逻辑函数
  • 独立的业务逻辑封装到各个函数内部
  • 函数内部把组件中需要用到的数据或者方法return出去
  • 组件中调用函数,把数据或者方法组合回来使用

image.png

新建两个js文件
在这里插入图片描述

// 封装banner轮播图相关的业务代码  useBanner.js
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}
}
//useCategory.js
// 封装分类数据业务相关代码
import { onMounted, ref } from 'vue'
import { getCategoryAPI } from '@/apis/category'
import { useRoute } from 'vue-router'
import { onBeforeRouteUpdate } from 'vue-router'export function useCategory() {// 获取分类数据const categoryData = ref({})const route = useRoute()const getCategory = async (id = route.params.id) => {const res = await getCategoryAPI(id)categoryData.value = res.result}onMounted(() => getCategory())// 目标:路由参数变化的时候 可以把分类数据接口重新发送onBeforeRouteUpdate((to) => {// 存在问题:使用最新的路由参数请求最新的分类数据getCategory(to.params.id)})return {categoryData}
}
<!--Category/index.vue-->
<script setup>
import GoodItem from '../Home/components/GoodItem.vue'
import { useBanner } from './composables/useBanner'
import { useCategory } from './composables/useCategory'
const { bannerList } = useBanner()
const { categoryData } = useCategory()
</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 class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in bannerList" :key="item"><img :src="item.imgUrl" alt=""></el-carousel-item></el-carousel></div><!-- 分类列表 --><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="goods in item.goods" :goods="goods" :key="goods.id" /></div></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;// position: absolute;margin: 0 auto; //居中left: 0;top: 0;z-index: 98;img {width: 100%;height: 500px;}
}
</style>

在这里插入图片描述

小结

本章对于Home和一级路由功能进行了实现
组件的静态结构也很有价值去研究和书写
love and peace
祝大家学习顺利
在这里插入图片描述

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

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

相关文章

2024年5月天润融通JAVA二面15-20K

二面 1、聊项目 2、举例说明你在上家公司职级晋升的原因 3、开发者和管理者的区别&#xff0c;你怎么做管理者 4、对sass的理解&#xff0c;包括流程&#xff0c;技术选型 5、springboot如何把bean加载到ioc容器中&#xff0c;ioc容器的理解 6、一万个任务同时执行&#…

内网安全之搭建ADCS证书服务

在域控上安装ADCS服务时&#xff0c;默认会自动配置完LDAPS&#xff0c;如果不是在域控上安装ADCS服务&#xff0c;需要手动配置LDAPS 安装证书服务ADCS 打开服务器管理器——>添加角色和功能 选择“基于角色或基于功能的安装”选项&#xff0c;然后点击下一步 选择“从…

网络协议——Modbus-RTU

目录 1、简介 2、消息格式 3、Modbus寄存器种类说明 4、功能码01H 5、功能码02H 6、功能码03H 7、功能码04H 8、功能码05H 9、功能码06H 10、功能码0FH 11、功能码10H 1、简介 Modbus-RTU&#xff08;Remote Terminal Unit&#xff09;是一种串行通信协议&#xff0…

云和恩墨海外首秀在吉隆坡召开的2024中国智能科技与文化展览会

作为中马建交50周年官方重点推荐的活动之一&#xff0c;2024中国智能科技与文化展览会&#xff08;第四届&#xff09;于5月20至21日在毗邻吉隆坡双子塔的吉隆坡国际会展中心举办。本次展览会获得马来西亚科学技术创新部、马来西亚通讯部、中国驻马来西亚大使馆和马来西亚中华总…

使用Unsloth微调Llama3-Chinese-8B-Instruct中文开源大模型

使用Unsloth微调Llama3-Chinese-8B-Instruct中文开源大模型 微调Llama3-Chinese-8B-InstructLlama-3-Chinese-8B-InstructUnsloth环境设置下载预训练模型加载model、tokenizer设置LoRA训练参数准备数据集数据处理训练超参数配置开始训练模型推理保存LoRA模型加载模型保存完整模…

OpsManage基于docker的部署与使用

前言 自动化运维管理工具OpsManagerhttp://mp.weixin.qq.com/s?__bizMzk0NTQ3OTk3MQ&mid2247487736&idx1&snefef3a930b88649033f61942a77f42d2&chksmc31598b4f46211a240ffc5360ae238b27d0f495fcbe8dc18abdbd79bc25c00726f74a7312dd0&scene21#wechat_redi…

[IMX6ULL驱动开发]-Linux对中断的处理(一)

目录 中断概念的引入 ARM架构中断的流程 异常向量表 Linux系统对中断的处理 ARM对程序和中断的处理 Linux进程中断处理 中断概念的引入 如何理解中断&#xff0c;我们可以进行如下抽象。把CPU看做一个母亲&#xff0c;当它正在执行任务的时候&#xff0c;可以看为是一个母…

【css】引入背景图时候,路径写入@会报错

看报错信息 我的写法 解决办法 在前面加个~

ThreadLocal原理及使用

一、引言 在Java多线程编程中&#xff0c;ThreadLocal是一个非常有用的工具&#xff0c;它提供了一种将对象与线程关联起来的机制&#xff0c;使得每个线程都可以拥有自己独立的对象副本&#xff0c;从而避免了线程安全问题。然而&#xff0c;使用不当会导致内存泄漏问题。 二…

【VTKExamples::Texture】第六期 TextureThreshold

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例TextureThreshold,并解析接口vtkTexture,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~Y…

python使用base加密解密

原理 base编码是一种加密解密措施&#xff0c;目前常用的有base16、base32和base64。其大致原理比较简单。 以base64为例&#xff0c;base64加密后共有64中字符。其加密过程是编码后将每3个字节作为一组&#xff0c;这样每组就有3*824位。将每6位作为一个单位进行编码&#xf…

MySQL主从复制+读写分离(ShardingJDBC)

MySQL主从复制读写分离 MySQL主从复制介绍二进制日志&#xff1a; MySQL的主从复制原理如下搭建主从复制准备工作主库配置从库配置 测试 读写分离案例ShardingJDBC介绍数据库环境初始工程导入读写分离配置测试1). 保存数据2). 修改数据3). 查询数据4). 删除数据 MySQL主从复制 …

vue3结合element-plus之如何优雅的使用表格

背景 表格组件的使用在后台管理系统中是非常常见的,但是如果每次使用表格我们都去一次一次地从 element-plus 官网去 复制、粘贴和修改成自己想要的表格。 这样一来也说得过去,但是如果我们静下来细想不难发现,表格的使用都是大同小异的,每次都去复制粘贴,对于有很多表格…

【日记】跟奇安信斗智斗勇,败下阵来(416 字)

正文 今天一个客户都没有&#xff0c;让我快怀疑我们银行是不是要倒闭了…… 因为内外网 u 盘不知所踪&#xff0c;所以重新制了一个。深刻体会到了奇安信有多烂。有两个 u 盘&#xff0c;奇安信似乎把主控写坏了&#xff0c;插上电脑有反应&#xff0c;但是看不见盘符&#xf…

Linux中vim的基本使用

目录 vim中的三种模式以及基本操作命令模式(默认模式)插入模式底行模式 命令模式下的命令底行模式下的命令 vim是Linux和Unix环境下最基本的文本编辑器&#xff0c;类似于windows上的记事本 vim和Visual studio相比&#xff0c;vim并不集成&#xff0c;vim只能用来写代码 VS把写…

CopyOnWriteArrayList原理分析

1.简介 JDK1.5之前&#xff0c;由于那个版本尚未退出专门运行在并发环境下的集合&#xff0c;对于List类型&#xff0c;我明只能选择Vector或者Stack这种老古董&#xff0c;但是它们效率太低(全部的增删改方法均被synchronized修饰着&#xff0c;那个时候的synchronized由于还…

【有手就行】使用你自己的声音做语音合成,CPU都能跑,亲测有效

此文介绍在百度飞桨上一个公开的案例&#xff0c;亲测有效。 厌倦了前篇一律的TTS音色了吗&#xff1f;打开短视频听来听去就是那几个声音&#xff0c;快来试试使用你自己的声音来做语音合成吧&#xff01;本教程非常简单&#xff0c;只需要你能够上传自己的音频数据就可以(建议…

Helm安装kafka3.7.0无持久化(KRaft 模式集群)

文章目录 2.1 Chart包方式安装kafka集群 5.开始安装2.2 命令行方式安装kafka集群 搭建 Kafka-UI三、kafka集群测试3.1 方式一3.2 方式二 四、kafka集群扩容4.1 方式一4.2 方式二 五、kafka集群删除 参考文档 [Helm实践---安装kafka集群 - 知乎 (zhihu.com)](https://zhuanlan.…

离散数学--图论

目录 1.简单概念 2.握手定理 3.点割集 4.边割集 5.点连通度和边连通度 6.Dijstra算法&&最短路径 7.有向图的连通性 8.图的矩阵表示 9.欧拉图问题 10.哈密尔顿图 1.简单概念 &#xff08;1&#xff09;这个里面的完全图比较重要&#xff0c;完全图是例如k3,k5这…

vue项目报错:internal/modules/cjs/loader.js:892 throw err;

前言&#xff1a; vue项目中无法正常使用git&#xff0c;并报错情况。 报错信息&#xff1a; internal/modules/cjs/loader.js:892throw err;^ Error: Cannot find module D:\project\sd_wh_yth_front\node_modules\yorkie\src\runner.js 报错处理&#xff1a; npm install y…