Article directory
Home page component structure
Component structure split
Five new components are added according to the structure: classification on the left, Banner, fresh and good things, popular recommendations, and product list. HomeCategory.vue
Create , HomeBanner.vue
, HomeNew.vue
, HomeHot.vue
, in sequence under the src/views/Home/components path HomeProduct.vue
:
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.vue
Add 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:
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
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
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">¥{
{ 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">¥{
{ item.price }}</p>
</RouterLink>
</li>
</ul>
</HomePanel>
</template>
Realization of Popular Recommendations
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-lazy
to 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.
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">¥{
{ 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-lazy
Replace 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
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">¥{
{ 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">¥{
{ 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>