Vue3 - 小兔仙 - day3

devtools/2024/11/16 4:50:00/

0.学习大纲

1.Home模块

静态结构搭建和分类实现

整体结构创建

按照结构新增五个组件,准备最简单的模版,分别在Home模块的入口组件中引入

  • HomeCategory

  • HomeBanner

  • HomeNew

  • HomeHot

  • HomeProduct

在组件中添加简单模版

<script setup>
</script><template><div> HomeCategory </div>
</template>

Home模块入口组件中引入并渲染

<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.vue
<script setup>
import { useCategoryStore } from "@/stores/category";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.id" 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></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>

banner轮播图实现

实现思路

获取数据渲染组件 

// /apis/home.js
import http from '@/utils/http'export function getBannerAPI() {return http({url: 'home/banner'})
}
// HomeBanner.vue
<script setup>
import { getBannerAPI } from "@/apis/home";
import { ref, onMounted } from "vue";// 获取轮播图数据
const bannerList = ref([]);
const getBanner = async () => {const res = await getBannerAPI();bannerList.value = res.result;
};
onMounted(() => {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><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>

面板组件封装

场景说明

问:组件封装解决了什么问题?

答:1.复用问题 2.业务维护问题

新鲜好物和人气推荐模块,在结构上非常相似,只是内容不同,通过组件封装可以实现复用结构的效果。 

核心思路:把可复用的结构只写一次,把可能发生的部分抽象成组件参数(props/插槽)

实现步骤

1.不做任何抽象,准备静态模版

2.抽象可变的部分

        - 主标题和副标题是纯文本,可以抽象成prop传入

        - 主体内容是复杂的模版,抽象成插槽传入

组件封装

// 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 name="main" /></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.js
/*** @description: 获取新鲜好物* @param {*}* @return {*}*/
export const findNewAPI = () => {return http({url: '/home/new'})
}/*** @description: 获取人气推荐* @param {*}* @return {*}*/
export const getHotAPI = () => {return http({url: '/home/hot'})
}
// HomeNew.vue
<script setup>
import HomePanel from "./HomePanel.vue";
import { findNewAPI } from "@/apis/home";
import { onMounted, ref } from "vue";// 获取数据
const newList = ref([]);const getNewList = async () => {const res = await findNewAPI();newList.value = res.result;
};onMounted(() => getNewList());
</script><template><HomePanel title="新鲜好物" sub-title="新鲜出炉 品质靠谱"><ul class="goods-list"><li v-for="item in newList" :key="item.id"><RouterLink :to="`/detail/${item.id}`"><img :src="item.picture" alt="" /><p class="name">{{ item.name }}</p><p class="price">&yen;{{ item.price }}</p></RouterLink></li></ul></HomePanel>
</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>
// 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();console.log(res);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 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>

图片懒加载指令实现

场景和指令用法

场景:电商网站的首页通常会很长,用户不一定能访问到页面靠下面的图片,这类图片通过懒加载手段可以做到只有进入视口区域才发送图片请求。

指令用法

指令实现

// /directive/index.js
// 定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'export const lazyPlugin = {install(app) {// 懒加载指令逻辑app.directive('img-lazy', {mounted(el, binding) {// el: 指令绑定的那个元素 img// binding: binding.value  指令等于号后面绑定的表达式的值  图片url// console.log(el, binding.value)const { stop } = useIntersectionObserver(el,([{ isIntersecting }]) => {// console.log(isIntersecting)if (isIntersecting) {// 进入视口区域el.src = binding.valuestop()}},)}})}
}

全局注册

// main.js
// 全局指令注册
import { lazyPlugin } from '@/directives'
app.use(lazyPlugin)

产品列表实现与小组件封装

// home.js
/*** @description: 获取所有商品模块* @param {*}* @return {*}*/
export const getGoodsAPI = () => {return http({url: '/home/goods'})
}
// GoodsItem.vue
<script setup>
defineProps({goods: {tppe: 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 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>
// HomeProduct.vue
<script setup>
import HomePanel from "./HomePanel.vue";
import { getGoodsAPI } from "@/apis/home";
import { onMounted, ref } from "vue";
import GoodsItem from "./GoodsItem.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 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="goods in cate.goods" :key="goods.id"><GoodsItem :goods="goods" /></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;}}}}
}
</style>

2.商品一级分类

整体功能认识和路由配置

点击不同的导航,进行切换

配置路由

根据传入 id 的不同进行切换

面包屑导航功能实现

封装接口

// /apis/category.js
import http from '@/utils/http'/*** @description: 获取分类数据* @param {*} id 分类id * @return {*}*/
export const getTopCategoryAPI = (id) => {return http({url: '/category',params: {id}})
}

页面实现

 

// /views/Category/index.vue
<script setup>
import { getTopCategoryAPI } from "@/apis/category";
import { onMounted, ref } 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;
};
onMounted(() => {getCategory();
});
</script><template><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>
</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轮播图功能实现

适配接口

// /apis/home.js
/***@description: 获取banner轮播图数据
* @param {*}
* @return {*}*/
export function getBannerAPI(params = {}) {// 默认为1 商品为2const { distributionSite = '1' } = paramsreturn http({url: 'home/banner',params: {distributionSite}})
}

 迁移首页Banner逻辑

// /views/Category/index.js
// 获取banner
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><!-- 轮播图 --><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>

激活状态控制与分类列表功能实现

激活状态控制

分类列表实现

// /views/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><divclass="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>      

路由缓存问题解决

当路由path一样,参数不同的时候会选择直接复用路由对应的组件。

解决方案:

  1. 给 routerv-view 添加key属性,破坏缓存

  2. 使用 onBeforeRouteUpdate钩子函数,做精确更新

方案一

问题:整个页面示例都被破坏,影响性能。

方案二

总结

1.路由缓存问题产生的原因是什么?

路由只有参数变化时,会复用组件实例。

2.两种方案都可以解决路由缓存问题,如何选择?

在意性能问题,选择 onBeforeUpdate,精细化控制

不在意性能问题,选择 key,简单粗暴

基于业务逻辑的函数拆分

有点像Java的抽出方法

// useCategory.js
// 封装分类数据业务相关代码
import { onMounted, ref } from 'vue'
import { getTopCategoryAPI } 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 getTopCategoryAPI(id)categoryData.value = res.result}onMounted(() => getCategory())// 目标:路由参数变化的时候 可以把分类数据接口重新发送onBeforeRouteUpdate((to) => {// 存在问题:使用最新的路由参数请求最新的分类数据getCategory(to.params.id)})return {categoryData}
}
// 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'})bannerList.value = res.result}onMounted(() => getBanner())return {bannerList}
}

引入使用

import { useBanner } from './composables/useBanner'
import { useCategory } from './composables/useCategory'
const { bannerList } = useBanner()
const { categoryData } = useCategory()

http://www.ppmy.cn/devtools/134344.html

相关文章

解析安卓镜像包和提取DTB文件的操作日志

概述 想查看一下安卓的镜像包里都存了什么内容 步骤 使用RKDevTool_v3.15对RK3528_DC_HK1_RBOX_K8_Multi_WIFI_13_20230915.2153.img解包 路径: 高级(Advancing) > 固件(firmware) > 解包(unpacking)得到\Output\Android\Image boot.imguboot.imgsuper.img 处理boot.…

前端人之网络通信概述

前端人之网络通信概述 介绍网络七层模型物理层链路层网络层传输层应用层 介绍 互联网的核心技术就是一系列协议&#xff0c;总称“互联网协议”&#xff0c;对电脑如何连接和组网作出详细的规定&#xff0c;理解了这些协议就理解了互联网的原理。 网络七层模型 互联网完成数…

Kafka节点服役和退役

1 服役新节点 1&#xff09;新节点准备 &#xff08;1&#xff09;关闭 bigdata03&#xff0c;进行一个快照&#xff0c;并右键执行克隆操作。 &#xff08;2&#xff09;开启 bigdata04&#xff0c;并修改 IP 地址。 vi /etc/sysconfig/network-scripts/ifcfg-ens33修改完…

【Qt实现虚拟键盘】

Qt实现虚拟键盘 &#x1f31f;项目分析&#x1f31f;实现方式&#x1f31f;开发流程 &#x1f31f;项目分析 需求&#xff1a;为Linux环境下提供可便捷使用的虚拟键盘OS环境&#xff1a;Windows 7/11、CentOS 7开发语言&#xff1a;Qt/C IDE&#xff1a;QtCreator 、Qt5.14.2功…

C++内存池实现

1.内存池概念 内存池就和其他的池数据&#xff08;如线程池&#xff09;结构类似&#xff0c;由程序维护一个“池”结构来管理程序使用的内存&#xff0c;然后根据需要从内存池中申请使用内存或者向内存池中释放内存&#xff0c;来达到高效管理内存的目的。 在一般的内存管理的…

《鸿蒙生态:开发者的机遇与挑战》

一、引言 在当今科技飞速发展的时代&#xff0c;操作系统作为连接硬件与软件的核心枢纽&#xff0c;其重要性不言而喻。鸿蒙系统的出现&#xff0c;为开发者带来了新的机遇与挑战。本文将从开发者的角度出发&#xff0c;阐述对鸿蒙生态的认知和了解&#xff0c;分析鸿蒙生态的…

spring 声明式事务

spring 声明式事务 Spring 的声明式事务管理主要通过Transactional注解来实现&#xff0c;它可以确保方法执行期间的原子性、一致性、隔离性和持久性&#xff08;即ACID特性&#xff09;。 以下是一个使用Transactional注解的简单示例&#xff1a; . 首先&#xff0c;确保你的S…

JVM学习之路(5)垃圾回收

目录 Java垃圾回收 方法区回收 方法区的回收 堆内存回收 引用计数法和可达性分析算法 查看GC Root 五种对象引用 软引用 ​编辑 弱引用 虚引用和终结器引用 垃圾回收算法&#xff1a; 垃圾回收算法的历史和分类 垃圾回收算法的评价标准 标记清除算法 复制算法 标记整理算法 分代…