Dark horse programmer front-end Vue3 Xiaotuxian e-commerce project - (4) Home page layout

Home page component structure

Component structure split

image.png

Five new components are added according to the structure: classification on the left, Banner, fresh and good things, popular recommendations, and product list. HomeCategory.vueCreate , HomeBanner.vue, HomeNew.vue, HomeHot.vue, in sequence under the src/views/Home/components path HomeProduct.vue:

image-20230621110557287

Import components in the Home module

Introduce and render each component in the entry component of the index.vue module of 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>

classification implementation

template code

HomeCategory.vueAdd the following code to the file :

<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>

render data

The JSON data format returned by the backend interface is as follows:

image-20230621114021325

Get the data and traverse the output:

<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" 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>

Implementation of banner carousel

template code

<script setup>

</script>

<template>
  <div class="home-banner">
    <!--使用 ElementPlus 的轮播图组件-->
    <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>

package interface

Create the src/apis/home.js file and write the method to get the API of the Banner image:

/**
 * @description: 获取banner图
 */
import http from '@/utils/http'
export function getBannerAPI() {
    
    
    return http({
    
    
        url: 'home/banner'
    })
}

render data

Get the data and traverse the output:

<script setup>
import { getBannerAPI } from '@/apis/home'
import { onMounted, ref } from 'vue'

const bannerList = ref([])

const getBanner = async () => {
  const res = await getBannerAPI()
  console.log(res)
  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>

Panel Assembly Packaging

The [Fresh Goods] and [Popular Recommendation] modules displayed on the page have the same distribution and the same code, but the displayed data is different, so the common parts can be extracted for reuse.

Core idea: Write the reusable structure only once, and abstract the part that may change into component parameters (popS/slot). Abstract mutable part:

  • The main title and subtitle are plain text, which can be abstracted into props and passed in
  • The main content is a complex template, which is abstracted into a slot and passed in

image-20230621144521559

Create common components for reuse

Create the HomePanel.vue file under the src/views/Home/components path, the code is as follows:

<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>

Extract topics and subtopics

Because the main title and subtitle are plain text, they can be abstracted into props and passed in. The code is as follows:

<script setup>
//定义 Props,主标题和副标题
defineProps({
    
    
    title: {
    
    
        type: String
    },
    subTitle: {
    
    
        type: String
    }
})
</script>

Use parameters in main code:

<div class="container">
  <div class="head">
    <!-- 主标题和副标题 -->
    <h3>
      {
   
   {title}}<small>{
   
   { subTitle }}</small>
    </h3>
  </div>
  <!-- 主体内容区域 插槽-->
  <slot/>
</div>

Fresh and good things come true

image-20230621142447186

template code

There is a previously created HomeNew.vue file under the src/views/Home/components path, add the following code:

<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>

package interface

In the src/apis/home.js file, write the method to get the [Fresh Goods] API:

/**
 * @description: 获取新鲜好物
 * @param {*}
 * @return {*}
 */
export function findNewAPI(){
    
    
    return http({
    
    
        url: '/home/new'
    })
}

render data

Get the data and traverse the output:

<script setup>
import HomePanel from './HomePanel.vue'
import { findNewAPI } from '@/apis/home'
import { ref, onMounted } from 'vue'

const newList = ref([])
const getNewList = async () => {
    const res = await findNewAPI()
    console.log(res)
    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="/">
                    <img :src="item.picture" alt="" />
                    <p class="name">{
   
   { item.name }}</p>
                    <p class="price">&yen;{
   
   { item.price }}</p>
                </RouterLink>
            </li>
        </ul>
    </HomePanel>
</template>

Realization of Popular Recommendations

image-20230621144221266

template code

There is a previously created HomeHot.vue file under the src/views/Home/components path, add the following code:

<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="item.alt" />
        <p class="name">{
   
   { item.title }}</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>

package interface

In the src/apis/home.js file, write the method to get the API of 【Popular Recommendation】:

/**
 * @description: 获取人气推荐
 * @param {*}
 * @return {*}
 */
export function findHotAPI(){
    
    
    return http({
    
    
        url:'/home/hot'
    })
}

render data

Get the data and traverse the output:

<script setup>
import HomePanel from './HomePanel.vue';
import { ref, onMounted } from 'vue'
import { findHotAPI } from '@/apis/home'

const hotList = ref([])
const getHotList = async() => {
    const res = await findHotAPI()
    console.log(res)
    hotList.value = res.result
}

onMounted(() => {
    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="item.alt" />
                    <p class="name">{
   
   { item.title }}</p>
                </RouterLink>
            </li>
        </ul>
    </HomePanel>
</template>

Implementation of lazy load instruction

The main.js entry file usually only does some initialization and should not contain too much logic code. The lazy loading instructions can be encapsulated as a plug-in through the plug-in method. The main.js entry file only needs to be responsible for registering the plug-in.

Encapsulate global directives

Create an index.js file in the src/directives/ directory, and customize the instructions in it v-img-lazyto load the image when it enters the viewport area:

// 定义懒加载插件
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.value
              stop()
            }
          },
        )
      }
    })
  }
}

register global directive

Register custom directives as global directives in main.js:

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

Image lazy loading

Use custom lazy loading instructions to modify the code of HomeHot.vue and HomeNew.vue files. After the modification is as follows:

<img v-img-lazy="item.picture" alt="" />

In this way, when the picture enters the viewport area, the address of the picture will be assigned to the src attribute of img.

Product product list implementation

Because the product list page also has a lot of module codes that can be reused, but the data is different, so we can still continue to use the packaged panel components.

image-20230621154128794

template code

Add the following code to the previously created HomeProduct.vue file:

<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>

package interface

In the src/apis/home.js file, encapsulate the interface for obtaining product list information:

/**
 * @description: 获取所有商品模块
 * @param {*}
 * @return {*}
 */
export function getGoodsAPI() {
    
    
    return http({
    
    
        url: '/home/goods'
    })
}

render data

Get the data and traverse the output:

<script setup>
import HomePanel from './HomePanel.vue'
import { getGoodsAPI } from '@/apis/home';
import { ref,onMounted } from 'vue';

const goodsProduct = ref([])
const getGoodList = async()=>{
    const res = await getGoodsAPI()
    goodsProduct.value = res.result
}

onMounted(()=>{
    getGoodList()
})

</script>

Image lazy loading

v-img-lazyReplace the original src of the img tag with the lazy load directive:

<!-- 指令替换 -->
<img v-img-lazy="cate.picture" />

<!-- 指令替换 -->
<img v-img-lazy="goods.picture" alt="" />

GoodsItem component package

image-20230621154957748

Many business modules of the Xiaotuxian project need to use the same product display module, so there is no need to repeat the definition and package it for easy reuse.

Original code:

<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>

Modified code:

<ul class="goods-list">
  <li v-for="goods in cate.goods" :key="item.id">
    <GoodsItem :good="good" />
  </li>
</ul>

Packaging components

Create the src\views\Home\components\GoodsItem.vue file, extract the reusable code, and abstract the product information into props parameters:

<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">&yen;{
   
   { good.price }}</p>
    </RouterLink>
</template>
<style 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>

use components

Modify the code of product information in src\views\Home\components\HomeProduct.vue:

<ul class="goods-list">
  <li v-for="good in cate.goods" :key="good.id">
    <GoodsItem :good="good" />
  </li>
</ul>

Guess you like

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