vue3学习日记7 - Home页面

最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录

视频网址:

Day2-17.Layout-Pinia优化重复请求_哔哩哔哩_bilibili

学习日记:

vue3学习日记1 - 环境搭建-CSDN博客

vue3学习日记2 - 组合式API基础学习-CSDN博客

vue3学习日记3 - 组合式API练习小案例-CSDN博客

vue3学习日记4 - Pinia-CSDN博客

vue3学习日记5 - 项目起步-CSDN博客

vue3学习日记6 - Layout-CSDN博客

一、整体结构拆分和分类实现

1、创建如下文件夹及目录

新建文件的内容分别为

html 复制代码
<template>
    Banner页面
</template>

<template>
    我是分类页
</template>

<template>
    人气推荐
</template>

<template>
    新鲜好物
</template>

<template>
    产品列表
</template>

index.vue

html 复制代码
<script setup>
import HomeCategory from './componments/HomeCategory.vue'
import HomeBanner from './componments/HomeBanner.vue'
import HomeNew from './componments/HomeNew.vue'
import HomeHot from './componments/HomeHot.vue'
import homeProduct from './componments/HomeProduct.vue'
</script>

<template>
  <div class="container">
    <HomeCategory />
    <HomeBanner />
  </div>
  <HomeNew />
  <HomeHot />
  <homeProduct />
</template>

运行截图

2、修改HomeCategory的页面

html 复制代码
<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>

运行截图

3、在HomeCategory页面中访问接口,将返回的数据渲染在页面上

html 复制代码
<script setup>
import { useCategoryStory } from '@/stores/category'
const categoryStory = useCategoryStory()
</script>

<template>
  <div class="home-category">
    <ul class="menu">
      <!-- 获取categoryList,修改一级标题名字 -->
      <li v-for="item in categoryStory.categoryList" :key="item.id">
        <!-- 修改一级标题名字 -->
        <RouterLink to="/">{{ item.name }}</RouterLink>
        <!-- 便利里面children,只展示前两个,将标题名字渲染 -->
        <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 :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>

运行结果

二、Banner轮播图

1、使用elemnetPlue中的轮播图

html 复制代码
<script setup>
import {getBannerAPI} from '@/apis/Home.js'
import { onMounted, ref } from 'vue'
// 设置响应式数据bannerList
const bannerList = ref([])
// 访问接口,将获取到的数据赋值给bannerList
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>

2、在api文件夹下新建以下文件

javascript 复制代码
import httpInstance from "@/utils/http";
// 获取BannerAPI
export function getBannerAPI(){
    return httpInstance({
        url:'/home/banner'
    })
}

3、运行截图

三、面板组件封装

1、核心思路

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

2、实现步骤

javascript 复制代码
1、不做任何抽象,准备静态模板 
2、抽象可变的部分 
    - 主标题和副标题是纯文本,可以抽象成prop传入 
    - 主题内容是复杂的目标那,抽象成插槽传入

3、新建文件

模板如下

html 复制代码
<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>

4、修改HomePanel部分代码

html 复制代码
<script setup>
defineProps({
    // 主标题
    title:{
        type:String
    },
    // 副标题
    typetitle:{
        type:String
    }
})
</script>
<template>
  <div class="home-panel">
    <div class="container">
      <div class="head">
         <!-- 主标题和副标题 -->
        <h3>
          {{title}}<small>{{typetitle}}</small>
        </h3>
      </div>
      <!-- 主体内容区域 -->
      <slot/>
    </div>
  </div>
</template>

5、在index.vue中修改部分代码

html 复制代码
<template>
  <div class="container">
    <HomeCategory />
    <HomeBanner />
  </div>
  <HomeNew />
  <HomeHot />
  <homeProduct />
  <HomePanel title="新鲜好物" typetitle="新鲜好物 值得推荐">
    <div>新鲜好物哈哈哈</div>
  </HomePanel>
  <HomePanel title="人气推荐" typetitle="人气推荐 值得推荐">
    <div>人气推荐哈哈哈</div>
  </HomePanel>
</template>

四、新鲜好物板块实现

1、修改HomeNew代码

html 复制代码
<script setup>
import HomePanel from './HomePanel.vue'
import {getNewAPI} from '@/apis/Home'
import { onMounted, ref } from 'vue'

const newList = ref([])
const getNew = async () => {
    const res = await getNewAPI()
    console.log(res)
    newList.value = res.result
}
onMounted(()=>getNew())
</script>

<template>
  <HomePanel title="新鲜好物" typetitle="新鲜好物 值得推荐">
    <!-- 下面是插槽主体内容模版 -->
    <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>
  </HomePanel>
  
</template>

2、修改Home.js代码,添加访问新鲜好物的数据

javascript 复制代码
import httpInstance from "@/utils/http";
// 获取BannerAPI
export function getBannerAPI(){
    return httpInstance({
        url:'/home/banner'
    })
}
// 获取新鲜好物API
export function getNewAPI(){
    return httpInstance({
        url:'/home/new'
    })
}

3、运行结果

五、人气推荐板块实现

1、修改HomeHot代码

html 复制代码
<script setup>
import HomePanel from './HomePanel.vue'
import {getHotAPI} from '@/apis/Home'
import { onMounted, ref } from 'vue'
const hotList = ref([])
const getHot = async() =>{
    const res = await getHotAPI()
    console.log(res,123)
    hotList.value = res.result
}
onMounted(()=>getHot())
</script>

<template>
  <HomePanel title="人气推荐" typetitle="人气爆款 不容错过">
      <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>

2、修改Home.js代码,添加访问新鲜好物的数据

javascript 复制代码
// 获取人气板块API
export function getHotAPI(){
    return httpInstance({
        url:'/home/hot'
    })
}

3、运行结果

六、图片懒加载指令实现

1、核心原理

图片进入视口才发送资源请求

2、在main.js中使用useIntersectionObserver检测目标元素的可见性

1、官网

useIntersectionObserver | VueUse 中文网 (nodejs.cn)

2、使用

javascript 复制代码
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import '@/styles/common.scss'
// useIntersectionObserver:检测目标元素的可见性
import { useIntersectionObserver } from '@vueuse/core'
const app = createApp(App)
app.use(createPinia())
app.use(router)

app.mount('#app')

// 定义全局指令
app.directive('img-lazy',{
    /**
     * 
     * @param {*} el : 指令绑定的哪个元素 img
     * @param {*} binding :binding.value 指令等于号后面绑定的表达式的值 图片url
     */
    mounted(el,binding){
        useIntersectionObserver(
            el,
            ([{ isIntersecting }])=>{
                // isIntersecting : 可以判断是否进入视口区域
                if( isIntersecting ){
                    el.src = binding.value
                }
            }
        )
    }
})

运行截图

七、懒加载优化

1、问题1:逻辑书写位置不合理

懒加载指令的逻辑不应该直接写到入口文件中,只是不合理的 入口文件通常制作一些初始化的事情,不应该包含太多的逻辑代码,可以通过插件的方法把懒加载指令为插件,main.js入口文件只需要负责注册插件即可

新建以下文件

javascript 复制代码
import { useIntersectionObserver } from '@vueuse/core'
// 定义全局指令
export const lazyPlugin = {
    install(app){
        app.directive('img-lazy',{
            /**
             * 
             * @param {*} el : 指令绑定的哪个元素 img
             * @param {*} binding :binding.value 指令等于号后面绑定的表达式的值 图片url
             */
            mounted(el,binding){
                const { stop } = useIntersectionObserver(
                    el,
                    ([{ isIntersecting }])=>{
                        // isIntersecting : 可以判断是否进入视口区域
                        if( isIntersecting ){
                            el.src = binding.value
                            // 停止useIntersectionObserver
                            stop()
                        }
                    }
                )
            }
        })
    }
}

2、问题2:重复监听问题

useIntersectionObserver对于元素的监听一直存在,除非手动停止监听,存在内存浪费

javascript 复制代码
                // 设置一个stop
                const { stop } = useIntersectionObserver(
                    el,
                    ([{ isIntersecting }])=>{
                        // isIntersecting : 可以判断是否进入视口区域
                        if( isIntersecting ){
                            el.src = binding.value
                            // 停止useIntersectionObserver
                            stop()
                        }
                    }
                )

3、测试一下

八、Product产品列表实现

1、添加接口

javascript 复制代码
// 获取Product数据
export function getProductAPI(){
    return httpInstance({
        url:'/home/goods'
    })
}

2、修改HomeProduct代码

javascript 复制代码
<script setup>
import HomePanel from './HomePanel.vue'
import { getProductAPI } from '@/apis/Home'
import { onMounted, ref } from 'vue'
const goodsProduct = ref([])
const getProduct = async () => {
    const res = await getProductAPI()
    goodsProduct.value = res.result
}
onMounted(()=>getProduct())
</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="good in cate.goods" :key="good.id">
            <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">&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>

3、运行截图

九、GoodItem组件封装

设置一个GoodsItem属于纯展示类组件,这类组件的封装思想就是:抽象Props参数,传入什么就显示什么

1、新建文件

html 复制代码
<script setup>
import {defineProps} from 'vue'
defineProps({
    good:{
        type:String
    }
})
</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">&yen;{{ good.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>

2、修改HomeProduct页面代码

html 复制代码
<script setup>
import HomePanel from './HomePanel.vue'
import { getProductAPI } from '@/apis/Home'
import { onMounted, ref } from 'vue'
import GoodsItem from './GoodsItem.vue'
const goodsProduct = ref([])
const getProduct = async () => {
    const res = await getProductAPI()
    goodsProduct.value = res.result
}
onMounted(()=>getProduct())
</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="good in cate.goods" :key="good.id">
            <GoodsItem :good="good"/>
          </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>

3、运行截图(和原来的没有变化,主要看其是否可以正常渲染)

相关推荐
细心的莽夫12 分钟前
SpringMVC复习笔记
java·spring boot·笔记·学习·spring
垃圾侠2 小时前
vue2版本tinymce简单使用指南
前端·vue.js
明月看潮生2 小时前
青少年编程与数学 02-006 前端开发框架VUE 25课题、UI数据
javascript·vue.js·ui·青少年编程·编程与数学
黄团团2 小时前
Vue2+OpenLayers实现车辆开始、暂停、重置行驶轨迹动画(提供Gitee源码)
前端·javascript·数据库·vue.js·gitee·html
Judy16233 小时前
Vue 实现当前页面刷新的几种方法
前端·javascript·vue.js
芳草萋萋鹦鹉洲哦3 小时前
【element plus】虚拟dom表格中cellRenderer如何使用v-for循环渲染item
前端·javascript·elementui·虚拟dom表格
吱吱吱0213 小时前
TouchGFX学习笔记(一)
java·笔记·学习
Lysun0013 小时前
vue3里面,事件触发一次,方法执行多次
javascript·vue.js·elementui·element-plus
游戏乐趣3 小时前
借助扣子AI辅导孩子学习英语
人工智能·学习
jjjxxxhhh1233 小时前
书法的学习过程
学习