前端Vue小兔鲜儿电商项目实战Day03
一、Home - 整体结构搭建和分类实现
1. 页面结构
①按照结构新增5个组件,准备最简单的模板,分别在Home模块的入口组件中引入
src/views/Home/components/
-
HomeCategory.vue
-
HomeBanner.vue
-
HomeNew.vue
-
HomeHot.vue
-
HomeProduct.vue
<script setup>
</script><template><div> HomeCategory </div>
</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>
2. 分类实现
①静态结构搭建 - src/views/Home.components/HomeCategory.vue
<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>
②数据渲染
<script setup>
import { useCategoryStore } from '@/stores/category.js'
const categoryStore = useCategoryStore()
</script><template><div class="home-category"><ul class="menu"><li v-for="item in categoryStore.categoryList" :key="item.id"><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 :src="i.picture" alt="" /><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></ul></div>
</template>
二、Home - banner轮播图功能实现
Carousel 走马灯 | Element Plus
1. 组件静态结构搭建
src/views/Home/components/HomeBanner.vue
<script setup></script><template><div class="home-banner"><el-carousel height="500px"><el-carousel-item v-for="item in 4" :key="item"><imgsrc="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>
2. 获取数据渲染组件
①封装接口 - src/apis/home.js
import instance from '@/utils/http.js'
export function getBannerAPI() {return instance({url: 'home/banner'})
}
②获取数据渲染模板 - HomeBanner.vue
<script setup>
import { getBannerAPI } from '@/apis/home'
import { ref } from 'vue'const bannerList = ref([])
const getBanner = async () => {const res = await getBannerAPI()// console.log(res)bannerList.value = res.result
}getBanner()
</script><template><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>
</template>
三、Home - 面板组件封装
1. 场景说明
组件封装解决了什么问题?
答:①复用问题;②业务维护问题
新鲜好物和人气推荐模块,在结构上非常相似,只是内容不同,通过组件封装可以实现复用结构的效果。
2. 组件封装
核心思路:把可复用的结构只写一次,把可能发生变化的部分抽象成组件参数(props / 插槽)
实现步骤:
①不做任何抽象,准备静态模板
②抽象可变的部分
- 主标题和副标题是纯文本,可以抽象成props传入
- 主体内容是复杂的模板,抽象成插槽传入
src/views/Home/components/HomePanel.vue
<script setup>
defineProps({title: {type: String,default: ''},subTitle: {type: String,default: ''}
})
</script><template><div class="home-panel"><div class="container"><div class="head"><!-- 主标题和副标题 --><h3>{{ title }}<small>{{ subTitle }}</small></h3></div><!-- 主体内容区域 --><slot></slot></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>
四、Home - 新鲜好物和人气推荐实现
1. 新鲜好物
1. 准备模板
<script setup>
import HomePanel from './HomePanel.vue'
</script><template><home-panel title="新鲜好物" subTitle="新鲜好物 好多商品"> </home-panel><!-- 下面是插槽主体内容模版<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">¥{{ 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 0.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>
2. 准备接口 - src/apis/home.js
// 获取新鲜好物
export const getNewAPI = () => {return instance({url: '/home/new'})
}
3. 获取数据渲染模板 - HomeNew.vue
<script setup>
import HomePanel from './HomePanel.vue'
import { getNewAPI } from '@/apis/home'
import { ref } from 'vue'const newList = ref([])
const getNewList = async () => {const res = await getNewAPI()newList.value = res.result
}getNewList()
</script><template><home-panel title="新鲜好物" subTitle="新鲜好物 好多商品"><!-- 具名插槽 --><template #main><!-- 下面是插槽主体内容模版 --><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">¥{{ item.price }}</p></RouterLink></li></ul></template></home-panel>
</template>
把HomePanel.vue里的插槽改成 具名插槽
2. 人气推荐
1. 封装接口 - src/apis/home.js
// 获取人气推荐
export const getHotAPI = () => {return instance.get('home/hot')
}
2. 获取数据渲染模板 - src/views/Home/components/HomeHot.vue
<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="人气爆款 不容错过"><!-- 具名插槽 --><template #main><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></template></HomePanel>
</template><style scoped lang="scss">
.goods-list {display: flex;justify-content: space-between;height: 426px;li {width: 306px;height: 406px;transition: all 0.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>
五、Home - 图片懒加载指令实现
1. 场景和指令用法
场景:电商网站的首页通常会很长,用户不一定会访问到页面靠下面的图片,这类图片通过懒加载优化手段可以做到只有进入视口区域才发送图片请求
指令用法:
<img v-img-lazy="item.picture" />
在图片img身上绑定指令,该图片只有在正式进入到视口区域时才会发送图片网络请求
2. 实现思路和步骤
核心原理:图片进入视口才发送资源请求
自定义指令:自定义指令 | Vue.js
useIntersectionObserver:useIntersectionObserver | VueUse
①定义全局指令 - main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'import App from './App.vue'
import router from './router'
// 引入初始化样式文件
import '@/styles/common.scss'
import { useIntersectionObserver } from '@vueuse/core'const app = createApp(App)app.use(createPinia())
app.use(router)app.mount('#app')// 定义全局指令
app.directive('img-lazy', {mounted(el, binding) {// el: 指定绑定的元素 img// binding: binding.value 指令等于号后面绑定的表达式的值 图片urlconsole.log(el, binding.value)useIntersectionObserver(el, ([{ isIntersecting }]) => {// console.log(isIntersecting)if (isIntersecting) {// 进入视口区域el.src = binding.valuestop()}})}
})
②组件中使用指令 - HomeHot.vue
<img v-img-lazy="item.picture" :src="item.picture" alt="" />
六、Home - 懒加载指令优化
问题1:逻辑书写位置不合理
问:懒加载指令的逻辑直接写到入口文件中,合理吗?
答:不合理,入口文件通常只做一些初始化的事情,不应该包含太多的逻辑代码,可以通过插件的方法把懒加载指令封装为插件,main.js入口文件只需要负责注册插件即可
①src/direactives/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, ([{ isIntersecting }]) => {// console.log(isIntersecting)if (isIntersecting) {// 进入视口区域el.src = binding.valuestop()}})}})}
}
②mian.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'import App from './App.vue'
import router from './router'
// 引入初始化样式文件
import '@/styles/common.scss'
// 引入懒加载指令插件并注册
import { lazyPlugin } from '@/direactives'const app = createApp(App)app.use(createPinia())
app.use(router)
app.use(lazyPlugin)app.mount('#app')
问题2:重复监听问题
useIntersectionObserver对于元素的监听是一直存在的,除非手动停止监听,存在内存浪费
解决思路:在监听的图片第一次完成加载之后就停止监听
const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {if( isIntersecting) {el.src = binding.valuestop()}}
}
七、Home - Product产品列表实现
1. Product产品列表
Product产品列表是一个常规的列表渲染,实现步骤如下:
①封装接口 - src/apis/home.js
// 获取所有商品模块
export const getGoodsAPI = () => {return instance({url: '/home/goods'})
}
②获取数据并渲染 - src/views/Home/components/HomeProduct.vue
<script setup>
import HomePanel from './HomePanel.vue'
import { getGoodsAPI } from '@/apis/home'
import { ref } from 'vue'const goodsProduct = ref([])
const getGoods = async () => {const res = await getGoodsAPI()console.log(res)goodsProduct.value = res.result
}
getGoods()
</script><template><div class="home-product"><HomePanel v-for="cate in goodsProduct" :key="cate.id" :title="cate.name"><template #main><div class="box"><RouterLink class="cover" to="/"><!-- <img :src="cate.picture" /> --><img v-img-lazy="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="" /> --><img v-img-lazy="good.picture" alt="" /><p class="name ellipsis">{{ good.name }}</p><p class="desc ellipsis">{{ good.desc }}</p><p class="price">¥{{ good.price }}</p></RouterLink></li></ul></div></template></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 0.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>
八、Home - GoodsItem组件封装
1. 为什么要封装GoodsItem组件
在小兔鲜项目的很多个业务模块中都需要用到同样的商品展示模块,没必要重复定义,封装起来,方便复用。
2. 如何封装
核心思想:把要显示的数据对象设计为props参数,传入什么数据对象就显示什么数据
①封装组件 - src/views/Home/components/GoodsItem.vue
<script setup>
defineProps({good: {type: Object,default: () => {}}
})
</script><template><RouterLink to="/" class="goods-item"><img v-img-lazy="good.picture" alt="" /><p class="name ellipsis">{{ good.name }}</p><p class="desc ellipsis">{{ good.desc }}</p><p class="price">¥{{ good.price }}</p></RouterLink>
</template><style lang="scss" scoped>
.goods-item {display: block;width: 220px;padding: 20px 30px;text-align: center;transition: all 0.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>
②导入使用组件 - src/views/Home/components/HomeProduct.vue
<script setup>
import HomePanel from './HomePanel.vue'
import { getGoodsAPI } from '@/apis/home'
import { ref } from 'vue'
import GoodsItem from './GoodsItem.vue'const goodsProduct = ref([])
const getGoods = async () => {const res = await getGoodsAPI()console.log(res)goodsProduct.value = res.result
}
getGoods()
</script><template><div class="home-product"><HomePanel v-for="cate in goodsProduct" :key="cate.id" :title="cate.name"><template #main><div class="box"><RouterLink class="cover" to="/"><!-- <img :src="cate.picture" /> --><img v-img-lazy="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"><goods-item :good="good"></goods-item></li></ul></div></template></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;}}}}
}
</style>
九、一级分类 - 整体认识和路由配置
1. 准备分类组件
src/views/Category/index.vue
<template><div>我是分类页</div>
</template>
2. 配置路由
src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login/index.vue'
import Layout from '@/views/Layout/index.vue'
import Home from '@/views/Home/index.vue'
import Category from '@/views/Category/index.vue'const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [{path: '/',component: Layout,children: [{path: '',component: Home},{path: 'category/:id',component: Category}]},{path: '/login',component: Login}]
})export default router
3. 配置导航区域链接
src/views/Layout/components/LayoutHeader.vue 以及 LayoutFixed.vue
<!-- 导航区域 --><ul class="app-header-nav"><liclass="home"v-for="item in categoryStore.categoryList":key="item.id"><RouterLink :to="`/category/${item.id}`">{{ item.name }}</RouterLink></li></ul>
十、一级分类 - 面包屑导航渲染
Breadcrumb 面包屑 | Element Plus
1. 封装接口 - src/apis/category.js
import instance from '@/utils/http.js'// 获取分类数据
export const getTopCategoryAPI = (id) => {return instance({url: 'category',params: { id }})
}
2. 渲染面包屑导航 - src/views/Category/index.vue
<script setup>
import { getTopCategoryAPI } from '@/apis/category.js'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'const categoryData = ref({})
const route = useRoute()const getCategory = async () => {const res = await getTopCategoryAPI(route.params.id)categoryData.value = res.result
}
getCategory()// 监听路由的变化
watch(route, () => {// 路由变化时,重新发请求getCategory()
})
</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><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>
十一、一级分类 - banner轮播图实现
1. 分类轮播图实现
分类轮播图和首页轮播图的区别只有一个,接口参数不同,其余逻辑完全一致
①接口适配 - src/apis/home.js
// 获取banner
export function getBannerAPI(params = {}) {// 默认为1 商品为2const { distributionSite = '1' } = paramsreturn instance({url: 'home/banner',params: {distributionSite}})
}
②迁移首页Banner逻辑 - src/views/Category/index.vue
<script setup>
import { getTopCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'const categoryData = ref({})
const route = useRoute()const getCategory = async () => {const res = await getTopCategoryAPI(route.params.id)categoryData.value = res.result
}
getCategory()// 监听路由的变化
watch(route, () => {// 路由变化时,重新发请求getCategory()
})// 获取banner
const bannerList = ref([])
const getBanner = async () => {const res = await getBannerAPI({distributionSite: '2'})console.log(res)bannerList.value = res.result
}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><!-- 轮播图 --><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;margin: 0 auto;img {width: 100%;height: 500px;}
}
</style>
十二、一级分类 - 激活状态显示和分类列表渲染
1. 激活状态显示
①src/views/Layout/components/LayoutHeader.vue以及LayoutFixed.vue
<RouterLink active-class="active" :to="`/category/${item.id}`">{{ item.name }}</RouterLink>
2. 分类列表渲染
分类的数据已经在面包屑导航实现的时候获取到了,只需要通过v-for遍历出来即可
src/views/Category/index.vue
<script setup>
import { getTopCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import GoodsItem from '../Home/components/GoodsItem.vue'const categoryData = ref({})
const route = useRoute()const getCategory = async () => {const res = await getTopCategoryAPI(route.params.id)categoryData.value = res.result
}
getCategory()// 监听路由的变化
watch(route, () => {// 路由变化时,重新发请求getCategory()
})// 获取banner
const bannerList = ref([])
const getBanner = async () => {const res = await getBannerAPI({distributionSite: '2'})// console.log(res)bannerList.value = res.result
}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><!-- 轮播图 --><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 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><divclass="ref-goods"v-for="item in categoryData.children":key="item.id"><div class="head"><h3>- {{ item.name }}-</h3></div><div class="body"><!-- 这里注意查看GoodsItem里的prop是什么,要对应 --><GoodsItem v-for="good in item.goods" :good="good" :key="good.id" /></div></div></div></div>
</template>
十三、一级分类 - 解决路由缓存问题
1. 什么是路由缓存问题
使用带有参数的路由时需要注意的是,当用户从/users/johnny导航到/users/jolyne时,相同的组件实例将被重复使用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会被调用。
问题:一级分类的切换正好满足上面的条件,组件实例复用,导致分类数据无法更新
解决问题的思路:1. 让组件实例不复用,强制销毁重建;2. 监听路有变化,变化之后执行数据更新操作。
2. 解决方案
方案一:给router-view添加key
以当前路由完整路径为key的值,给router-view组件绑定,破坏缓存
src/views/Layout/index.vue
<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
import LayoutFixed from './components/LayoutFixed.vue'
import { useCategoryStore } from '@/stores/category.js'// 触发获取导航列表的action
const categoryStore = useCategoryStore()
// 一进页面就调用
categoryStore.getCategory()
</script><template><LayoutFixed /><LayoutNav /><LayoutHeader /><!-- 给二级路由出口添加key,破坏复用机制,强制销毁重建 --><RouterView :key="$route.fullPath" /><LayoutFooter />
</template>
方案二:使用onBeforeRouteUpdate钩子函数,做精确更新
API 文档 | Vue Router
onBeforeRouteUpdate钩子函数可以再每次路由更新之前执行,在回调中执行需要数据更新的业务逻辑即可,或者使用onBeforeRouteUpdate导航守卫,它也可以取消导航
src/views/Category/index.vue
<script setup>
import { getTopCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import GoodsItem from '../Home/components/GoodsItem.vue'
import { onBeforeRouteUpdate } from 'vue-router'const categoryData = ref({})
const route = useRoute()const getCategory = async (id = route.params.id) => {const res = await getTopCategoryAPI(id)categoryData.value = res.result
}
getCategory()// 目标:路由参数变化时,把分类数据接口重新发送
onBeforeRouteUpdate((to) => {// console.log('路由变化了')// 使用最新的路由参数请求最新的分类数据getCategory(to.params.id)
})// 获取banner
const bannerList = ref([])
const getBanner = async () => {const res = await getBannerAPI({distributionSite: '2'})// console.log(res)bannerList.value = res.result
}getBanner()
</script>
方案三:使用watch侦听器,监视路由的变化
<script setup>
import { getTopCategoryAPI } from '@/apis/category.js'
import { getBannerAPI } from '@/apis/home.js'
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import GoodsItem from '../Home/components/GoodsItem.vue'const categoryData = ref({})
const route = useRoute()const getCategory = async () => {const res = await getTopCategoryAPI(route.params.id)categoryData.value = res.result
}
getCategory()// 监听路由的变化
watch(route, () => {// 路由变化时,重新发请求getCategory()
})// 获取banner
const bannerList = ref([])
const getBanner = async () => {const res = await getBannerAPI({distributionSite: '2'})// console.log(res)bannerList.value = res.result
}getBanner()
</script>
3. 总结
①路由缓存问题产生的原因时什么?
答:路由只有参数变化时,会复用组件实例
②三种方案都可以解决路由缓存问题,如何选择呢?
答:如果在意性能问题,选择onBeforeUpdate,精细化控制;如果不在意性能问题,选择key,简单粗暴。
watch监听是即时触发的,可以在参数变化时立即获取最新数据,有利于实时更新页面展示;onBeforeUpdate在路由参数发生变化之后才会触发,因此可能存在一些延迟,不如watch及时。
十四、一级分类 - 使用逻辑函数拆分业务
概念理解:基于逻辑函数拆分业务是指把同一个组件中独立的业务代码通过函数做封装处理,提升代码的可维护性。
实现步骤:
①按照业务声明以`use`开头的逻辑函数
②把独立的业务逻辑封装到各个函数内部
③函数内部组件中需要用到的数据或者方法return出去
④在组件中调用函数把数据或者方法组合回来使用
src/views/Category/composables/useBanner.js
// 封装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/composables/useCategory.js
// 封装分类数据业务相关代码
import { onMounted, ref } from 'vue'
import { getTopCategoryAPI } from '@/apis/category.js'
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 getTopCategoryAPI(id)categoryData.value = res.result}onMounted(() => getCategory())// 目标:路由参数变化的时候 可以把分类数据接口重新发送onBeforeRouteUpdate((to) => {// 存在问题:使用最新的路由参数请求最新的分类数据getCategory(to.params.id)})return {categoryData}
}
src/views/Category/index.vue
<script setup>
import GoodsItem from '../Home/components/GoodsItem.vue'
import { useBanner } from './composables/useBanner'
import { useCategory } from './composables/useCategory'
const { bannerList } = useBanner()
const { categoryData } = useCategory()
</script>
相关文章:

前端Vue小兔鲜儿电商项目实战Day03
一、Home - 整体结构搭建和分类实现 1. 页面结构 ①按照结构新增5个组件,准备最简单的模板,分别在Home模块的入口组件中引入 src/views/Home/components/ HomeCategory.vue HomeBanner.vue HomeNew.vue HomeHot.vue HomeProduct.vue <script …...

ORACLE 查询SQL优化
1 使用EXPLAIN PLAN 使用EXPLAIN PLAN查看查询的执行计划,这可以帮助你理解查询是如何被Oracle执行的。基于执行计划,你可以确定是否存在索引缺失、不必要的全表扫描等问题。 以下是几种使用EXPLAIN PLAN的方法: 使用EXPLAIN PLAN FOR: 你可以…...

Ansible03-Ansible Playbook剧本详解
目录 写在前面5. Ansible Playbook 剧本5.1 YAML语法5.1.1 语法规定5.1.2 示例5.1.3 YAML数据类型 5.2 Playbook组件5.3 Playbook 案例5.3.1 Playbook语句5.3.2 Playbook1 分发hosts文件5.3.3 Playbook2 分发软件包,安装软件包,启动服务5.3.3.1 任务拆解…...

Qt-qrencode生成二维码
Qt-qrencode开发-生成二维码📀 文章目录 Qt-qrencode开发-生成二维码📀[toc]1、概述📸2、实现效果💽3、编译qrencode🔍4、在QT中引入编译为静态库的QRencode5、在Qt中直接使用QRencode源码6、在Qt中使用QRencode生成二…...

长安链使用Golang编写智能合约教程(三)
本篇主要介绍长安链Go SDK写智能合约的一些常见方法的使用方法或介绍 资料来源: 官方文档官方示例合约库 官方SDK接口文档 教程一:智能合约编写1 教程二:智能合约编写2 一、获取参数、获取状态、获取历史记录的方法解析 注意! …...

Vercel deploy- Nextjs project error-URL link-env variable
Vercel deploy- Nextjs project error-URL link-env variable Error Check Database URL Check next-auth URL NEXTAUTH_URLhttps://yourappname.vercel.app/ 依次排查可能性 Application error: a server-side exception has occurred (see the server logs for more in…...

Java | Leetcode Java题解之第123题买卖股票的最佳时机III
题目: 题解: class Solution {public int maxProfit(int[] prices) {int n prices.length;int buy1 -prices[0], sell1 0;int buy2 -prices[0], sell2 0;for (int i 1; i < n; i) {buy1 Math.max(buy1, -prices[i]);sell1 Math.max(sell1, b…...

Ubuntu22.04之扩展并挂载4T硬盘(二百三十三)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...

Redis实现延迟队列
最近用到一个延迟消息的功能,第一时间想到使用MQ或者MQ的插件,因为数据量不大,所以尝试使用Redis来实现了,毕竟Redis也天生支持类似MQ的队列消费,所以,在这里总结了一下Redis实现延迟消息队列的方式。 一、…...

如何准确查找论文数据库?
在学术研究过程中,查找相关论文是获取最新研究成果、支持自己研究的重要途径。准确查找论文数据库不仅可以节省时间,还能确保找到高质量的学术资源。本文将介绍一些有效的方法和策略,帮助您准确查找论文数据库。 1. 选择合适的数据库 不同的…...

翻译《The Old New Thing》- What a drag: Dragging a virtual file (IStream edition)
What a drag: Dragging a virtual file (IStream edition) - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080319-00/?p23073 Raymond Chen 2008年03月19日 拖拽虚拟文件(IStream 版本) 上一次,我们看…...

【FPGA】Verilog语言从零到精通
接触fpga一段时间,也能写点跑点吧……试试系统地康康呢~这个需要耐心但是回报巨大的工作。正原子&&小梅哥 15_语法篇:Verilog高级知识点_哔哩哔哩_bilibili 1Verilog基础 Verilog程序框架:模块的结构 类比:c语言的基础…...

unity打包的WebGL部署到IIS问题
部署之后会出错,我遇到的有以下几种; 进度条卡住不动 明明已经部署到了IIS上,为什么浏览网页的时候还是过不去或者直接报错。 进度条卡住不动的问题其实就是wasm和data的错误。 此时在浏览器上按F12进入开发者模式查看错误(下图…...

GPT-4o:人工智能的新里程碑
GPT-4o,作为OpenAI最新推出的人工智能技术,无疑在人工智能领域掀起了新一轮的浪潮。这款新型的语言模型不仅继承了GPT系列的核心优势,更在多个方面实现了突破性的进展。以下,我们将从版本间的对比分析、GPT-4o的技术能力以及个人整…...

发现一个ai工具网站
网址 https://17yongai.com/ 大概看了下,这个网站收集的数据还挺有用的,有很多实用的ai教程。 懂ai工具的可以在这上面找找灵感。...

第二十五章新增H5基础(以及视频~兼容)
1.HTML5中新增布局标签 HTML5新增了页眉,页脚,内容块等文档结构相关标签,可以使文档结构更加清晰明了。 1.新增的结构标签 1、<header>标签 定义文档或者文档中内容块的页眉。通常可以包含整个页面或一个内容区域的标题,…...

[英语单词] production quality
Our goal is to implement a production quality switch platform that supports standard management interfaces and opens the forwarding functions to programmatic extension and control. 说在openswitch的文档里有说这两词,含义是产品质量。是production修…...

windows安装nodeJs,以及常用操作
1. 官网(Node.js — Run JavaScript Everywhere (nodejs.org))下载想要安装的node版本 的安装包完成安装 2.环境变量设置: 系统变量: Path新增:D:\Program Files\nodejs (node安装目录) 3.设置淘宝源: npm config set registr…...

MySql part1 安装和介绍
MySql part1 安装和介绍 数据 介绍 什么是数据库,数据很好理解,一般来说数据通常是我们所认识的 描述事物的符号记录, 可以是数字、 文字、图形、图像、声音、语言等,数据有多种形式,它们都以经过数字化后存入计算机…...

SpringBoot打war包并配置外部Tomcat运行
简介 由于其他原因,我们需要使用SpringBoot打成war包放在外部的Tomcat中运行,本文就以一个案例来说明从SpringBoot打war包到Tomcat配置并运行的全流程经过 环境 SpringBoot 2.6.15 Tomcat 8.5.100 JDK 1.8.0_281 Windows 正文 一、SpringBoot配置打war包 第一步&a…...

2024.5.31每日一题
LeetCode 找出缺失的重复数字 题目链接:2965. 找出缺失和重复的数字 - 力扣(LeetCode) 题目描述 给你一个下标从 0 开始的二维整数矩阵 grid,大小为 n * n ,其中的值在 [1, n2] 范围内。除了 a 出现 两次ÿ…...

Oracle 数据库 varchar2 从 4000 扩展到 32k
Oracle 数据库 varchar2 从 4000 扩展到 32k 0. 引言1. 扩展 varchar2 支持长度2. 测试 0. 引言 今天来个项目需求,有1个字段的存储内容大概1万字。 当然其中1个方法是将这个字段的内容切分成几个字段,还有1个方法就是将 varchar2 默认支持 4000 的能力…...

postgressql——事务提交会通过delayChkpt阻塞checkpoint(9)
事务提交会通过delayChkpt阻塞checkpoint Postgresql事务在事务提交时(执行commit的最后阶段)会通过加锁阻塞checkpoint的执行,尽管时间非常短,分析为什么需要这样做: 首先看提交堆栈 #1 0x0000000000539175 in Co…...

开发者工具-sources(源代码选项)
一、概要说明 源代码面板从视觉效果上分为三个区域:菜单区、内容区、监听区。 菜单区里面有5个子分类: 网页(Page):指页面源,包含了该页面中所有的文件,即使多个域名下的文件也都会展示出来,包括iframe…...

没有 rr 头的 kamailio 路由脚本
分享下笔者最近编写的 kamailio 路由脚本 不用 rr 模块,因为有些 sip 协议栈不支持 rr 头处理 sip 注册直接回 200 OK,这部分目前不是重点更换 contact 头,换成 kamailio 自己目前只支持 sip transport 为 udp,以后可能支持 tcp&…...

mysql 分区
目标 给一个表(半年有800万)增加分区以增加查询速度 约束 分区不能有外键否则会报错 https://blog.csdn.net/yabingshi_tech/article/details/52241034 主键 按照时间列进行分区 https://blog.csdn.net/winerpro/article/details/135736454 参看以…...

在龙芯安装docker compose
安装过程报错:pynacl无法安装 原因:未知 解决尝试:单独安装pynacl 执行:pip install pynacl 报错: 再次执行dockerscompose撒谎啥 少了头文件 dev,表示c编译器有问题 python是c编写的 喵的 搞了半天是我…...

纯C++做多项式拟合
一、多项式拟合用途 当前有一组对应的x、y数据,希望通过这些数据点做出近似的多项式曲线:YnX^2mXc 其中多项式最高次数可调,返回各个参数及曲线的拟合度R^2 二、函数实现 参数中的order为设置的多项式最高次次数,coefficients为…...

vulnhub靶场之FunBox-9
一.环境搭建 1.靶场描述 Its a box for beginners, but not easy. Gather careful !!! Hint: Dont waste your time ! Every BruteForce-Attack at all ports can be stopped after 1500 trys per account. Enjoy the game and WYSIWYG ! This works better with VirtualBox…...

C# 变量与参数详解
在C#编程中,变量和参数是构建程序逻辑的基础。本篇博客将深入探讨C#中的变量作用域、参数传递方式、以及一些高级特性,如in、ref、out参数,params修饰符,可选参数和命名参数等。 变量作用域 在C#中,变量的作用域分为…...