vue3 之 商城项目—home

home—整体结构搭建

在这里插入图片描述
根据上面五个模块建目录图如下:
在这里插入图片描述

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>
分类实现

在这里插入图片描述
在这里插入图片描述
home/HomeCategory

<script setup>
import { useCategoryStore } from '@/stores/categoryStore'
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>


<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轮播功能实现

在这里插入图片描述
home/components/homeBanner

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



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

apis/home.js

import httpInstance from '@/utils/http'
// 获取banner
export function getBannerAPI (params = {}) {
  // 默认为1 商品为2
  const { distributionSite = '1' } = params
  return httpInstance({
    url: '/home/banner',
    params: {
      distributionSite
    }
  })
}

面板组件封装

场景说明
组件封装解决什么问题?
1️⃣复用问题
2️⃣业务维护问题
新鲜好物和人气推荐模块,在结构上非常相似,只是内容不同,通过组件封装可以实现复用结构的效果
在这里插入图片描述
组件封装
核心思路:把可复用的结构只写一次,把可能发生变化的部分抽象成组件参数(props/插槽)

实现步骤
1️⃣不做任何抽象,准备静态模版
2️⃣抽象可变的部分
主标题和副标题是纯文本,可以抽象成prop传入
主题内容是复杂的模版,抽象成插槽传入

Home/components/HomePanel.vue

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

</script>


<template>
  <div class="home-panel">
    <div class="container">
      <div class="head">
        <!-- 主标题和副标题 -->
        <h3>
          {{ title }}<small>{{ subTitle }}</small>
        </h3>
      </div>
      <!-- 主体内容区域 -->
      <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>

总结
非复杂的模版抽象成props,复杂的结构模版抽象为插槽

实现新鲜好物

在这里插入图片描述
在这里插入图片描述


Home/components/HomeNew.vue

//引入上面封装好的homePanel组件
<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>
  <!-- 下面是插槽主体内容模版
  <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>

图片懒加载指令是实现

场景
电商网站的首页通常会很长,用户不一定访问到页面靠下面的图片,这类图片通过懒加载优化手段可以做到只有进入视口区域才发送图片请求
在这里插入图片描述
实现思路和步骤
核心原理:图片进入视口才发送资源请求
在这里插入图片描述
在这里插入图片描述
directives/index.js

// 定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'
export const lazyPlugin = {
  install (app) {
  //install固定写法
    // 懒加载指令逻辑
    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
              //useIntersectionObserver对于元素的监听是一直存在的,除非手动停止监听,存在内存浪费
             // 解决思路:在监听的图片第一次完成加载之后就停止监听
              stop()
            }
          },
        )
      }
    })
  }
}

main.js

// 引入懒加载指令插件并且注册
import { lazyPlugin } from '@/directives'
app.use(lazyPlugin)
app.mount('#app')

product产品列表实现

在这里插入图片描述
Home/components/HomeProduct

<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 :src="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="good.id">
            <RouterLink to="/" class="goods-item">
              <img :src="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>
          </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>

GoodsItem组件封装

为什么封装goodsItem
在这里插入图片描述
如何封装
在这里插入图片描述
Home/components/GoodItems

<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 .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/components/HomeProduct

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/383489.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS 状态管理装饰器 Observed与ObjectLink 处理嵌套对象/对象数组 结构双向绑定

本文 我们还是来说 两个 harmonyos 状态管理的装饰器 Observed与ObjectLink 他们是用于 嵌套对象 或者 以对象类型为数组元素 的数据结构 做双向同步的 之前 我们说过的 state和link 都无法捕捉到 这两种数据内部结构的变化 这里 我们模拟一个类数据结构 class Person{name:…

java实现文件随机加密

1、引言 有时候我们需要对我们的某些文件数据进行加密&#xff0c;并且不希望被轻易破译&#xff0c;此时最好不要使用已知的加密方法&#xff0c;这里我就给大家提供一种数据加密的方式&#xff0c;用以实现文件数据的加密&#xff0c;我称之为随机加密&#xff0c;即使是对相…

【ES6】模块化

nodejs遵循了CommonJs的模块化规范 导入 require() 导出 module.exports 模块化的好处&#xff1a; 模块化可以避免命名冲突的问题大家都遵循同样的模块化写代码&#xff0c;降低了沟通的成本&#xff0c;极大方便了各个模块之间的相互调用需要啥模块&#xff0c;调用就行 …

vulnhub-->hacksudo-Thor靶机详细思路

目录 1. IP探测2.端口服务扫描3.网站漏洞扫描4.目录扫描5.信息分析6.破壳漏洞(Shellshock)nmap---漏洞检测CVE-2014-6271 7.nc反弹8.提权9.service提权 1. IP探测 ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:10:3c:9b, IPv4: 19…

C++入门学习(二十七)跳转语句—break语句

1、与switch语句联合使用 C入门学习&#xff08;二十三&#xff09;选择结构-switch语句-CSDN博客 #include <iostream> #include <string> using namespace std;int main() { int number;cout<<"请为《斗萝大路》打星(1~5※)&#xff1a;" &…

grab,一个强大的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个强大的 Python 库 - grab。 Github地址&#xff1a;https://github.com/lorien/grab Python Grab 是一个功能强大的 Web 抓取框架&#xff0c;它提供了丰富的功能和灵活的接口&#xff0c;使得…

Zabbix报警机制、配置钉钉机器人、自动发现、主动监控概述、配置主动监控、zabbix拓扑图、nginx监控实例

目录 配置告警 用户数超过50&#xff0c;发送告警邮件 实施 验证告警配置 配置钉钉机器人告警 创建钉钉机器人 编写脚本并测试 添加报警媒介类型 为用户添加报警媒介 创建触发器 创建动作 验证 自动发现 配置自动发现 主动监控 配置web2使用主动监控 修改配置文…

第80讲订单管理功能实现

后端 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace"com.java1234.mapper.OrderM…

计网day1

RTT&#xff1a;往返传播时延&#xff08;越大&#xff0c;游戏延迟&#xff09; 一.算机网络概念 网络&#xff1a;网样的东西&#xff0c;网状系统 计算机网络&#xff1a;是一个将分散得、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功…

KingSCADA实现按钮点击效果

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 在做SCADA项目的时候&#xff0c;按钮是不可缺少的功能&#xff0c;但软件自带的按钮太丑&#xff0c;已经无法满足现如今客户对界面美观度的要求。 这时候就需要UI小姐姐设计美观大气的SCADA界面&#xff0c;但UI设计…

尚硅谷 Vue3+TypeScript 学习笔记(下)

目录 五、组件通信 5.1. 【props】 5.2. 【自定义事件】 5.3. 【mitt】 5.4.【v-model】 5.5.【$attrs】 5.6. 【$refs、$parent】 5.7. 【provide、inject】 5.8. 【pinia】 5.9. 【slot】 1. 默认插槽 2. 具名插槽 3. 作用域插槽 六、其它 API 6.1.【shallowR…

Junit常用注解

注解是方法的“标签” 说明每个方法的“职责” Q:总共有那些注解? 参见官方的API文档 0.常用主机及其特点 BeforeClass 只会执行一次必须用static修饰常用来初始化测试需要的变量 Before 会执行多次&#xff08;只要写一次&#xff09;在每个Test执行执行之前执行可以和…

【Python】Mac 本地部署 stable-diffusion

其实要在本地部署 stable-diffusion 不难&#xff0c;只要有“魔法”一切都水到渠成&#xff0c;如下图&#xff1a; (base) MacBook-Pro python % git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui Cloning into stable-diffusion-webui... remote: Enu…

微信小程序 民宿预订租赁系统uniApp

通过山青水磨APP办理租房相关业务&#xff0c;线上解决预定、退订的业务&#xff0c;旅客在使用时更加灵活&#xff0c;实现了快速找房&#xff0c;在线沟通、便捷租赁等操作&#xff0c;除此以外&#xff0c;还能帮助旅客获取周边资讯、当地特色活动服务&#xff0c;提升旅客的…

linux系统下vscode portable版本的c++/Cmake环境搭建001

linux系统下vscode portable版本的Cmake环境搭建 vscode portable 安装安装基本工具安装 build-essential安装 CMake final script code安装插件CMake Tools & cmakeC/C Extension Pack Testsettings,jsonCMakeLists.txt调试和运行工具 CG 目的&#xff1a;希望在获得一个新…

重温阿里云宝塔面板部署前后端项目

首先祝大家新年快乐啊&#xff01; 回到老家&#xff0c;便打算趁这一段空闲时间提升一下自己&#xff0c;重点是学习实践一下echarts相关内容&#xff0c;很多公司项目都需要实现可视化&#xff0c;所以在bilibili上找了黑马的一个教程开始学习&#xff0c;不同的是&#xff…

书生谱语-全链条开发工具

书生谱语全链条开发体系 包含&#xff1a;数据、模型预训练、模型微调、模型量化部署、模型测评、模型场景应用全链路开发体系 github链接 通用大模型 国内外大语言模型快速发展&#xff0c;涌现了大量的大语言模型以及一批创业公司 深度学习模型的发展 大模型利用多模态优势…

PKI - 借助Nginx 实现Https_使用CA签发证书

文章目录 Pre概述操作步骤1. 生成 CA 密钥对2. 生成自签名的 CA 证书3. 生成服务器密钥对和证书签名请求 (CSR)4. 使用 CA 签署服务器证书 Nginx Https 自签证书1. 生成自签名证书和私钥2. 配置 Nginx 使用 CA签发的 HTTPS 证书3. 重启 Nginx 服务4. 直接访问5. 不验证证书直接…

第一篇【传奇开心果微博文系列】Python微项目技术点案例示例:pillow库实现毛笔字春联

传奇开心果微博文系列 系列微博文目录Python微项目技术点案例示例系列 微博文目录一、微项目目标二、实现微项目编程思路三、初步实现目标示例代码四、添加背景色、边框、阴影效果示例代码五、添加花纹背景、装饰线条示例代码六、添加花朵、插图等示例代码 系列微博文目录 Pyt…

PKI - 借助Nginx实现_客户端使用CA根证书签发客户端证书

文章目录 Pre概述步骤1. 创建根证书2. 生成客户端证书3. 准备客户端证书扩展文件4. 签发客户端证书5. 配置Nginx5. 重启 Nginx6. 测试 SAN 证书扩展案例&#xff1a;使用IP访问 Pre PKI - 借助Nginx 实现Https 服务端单向认证、服务端客户端双向认证 PKI - 数字签名与数字证书…