前端学习笔记 7:小兔鲜

前端学习笔记 7:小兔鲜

准备工作

创建项目

创建项目:

npm init vue@latest

相关选项如下:

image-20240110103054731

src目录下添加以下目录:

image-20240110103546382

别名路径联想

默认情况下在 VSCode 中输入import xxx from '@...'时不会启用路径联想功能,要启用需要在项目根目录下添加 VSCode 配置文件jsconfig.json

{
   
  "compilerOptions" : {
   
    "baseUrl" : "./",
    "paths" : {
   
      "@/*":["src/*"]
    }
  }
}

如果 VSCode 已经自动创建该文件,可以跳过这一步。

添加 ElementPlus

ElementPlus 加入的方式分为全部引入和按需引入,后者可以减少项目打包后的体积,所以这里采用按需引入

安装 ElementPlus:

npm install element-plus --save

安装插件:

npm install -D unplugin-vue-components unplugin-auto-import

修改vite.config.js,添加以下内容:

// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
    ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
   
  // ...
  plugins: [
    // ...
    AutoImport({
   
      resolvers: [ElementPlusResolver()],
    }),
    Components({
   
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

修改App.vue进行验证:

<script setup>
</script>

<template>
  <el-button type="primary">Primary</el-button>
</template>

定制主题色

安装 sass:

npm i sass -D

添加主题色样式文件styles/element/index.scss

/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      // 主色
      'base': #27ba9b,
    ),
    'success': (
      // 成功色
      'base': #1dc779,
    ),
    'warning': (
      // 警告色
      'base': #ffb302,
    ),
    'danger': (
      // 危险色
      'base': #e26237,
    ),
    'error': (
      // 错误色
      'base': #cf4444,
    ),
  )
)

修改vite.config.js

export default defineConfig({
   
  plugins: [
    // ...
    Components({
   
      resolvers: [ElementPlusResolver({
    importStyle: 'sass' })],
    }),
  ],
  // ...
  css: {
   
    preprocessorOptions: {
   
      scss: {
   
        // 自动导入定制化样式文件进行样式覆盖
        additionalData: `
          @use "@/styles/element/index.scss" as *;
        `,
      }
    }
  }
})

Axios 基础配置

最好在框架代码中创建 Axios 实例,并进行统一配置,这样可以对所有接口调用都要用的配置信息进行统一管理。

安装:

npm i axios

添加utils/http.js

import axios from 'axios'

// 创建axios实例
const http = axios.create({
   
  baseURL: 'http://pcapi-xiaotuxian-front-devtest.itheima.net',
  timeout: 5000
})

// axios请求拦截器
http.interceptors.request.use(config => {
   
  return config
}, e => Promise.reject(e))

// axios响应式拦截器
http.interceptors.response.use(res => res.data, e => {
   
  return Promise.reject(e)
})


export default http

添加测试代码apis/test.js

import http from '@/utils/http'

export const getCategoryService = () => {
   
    return http.get('home/category/head')
}

App.vue中进行测试:

import {
    getCategoryService } from '@/apis/test'
getCategoryService().then((res) => {
   
  console.log(res)
})

路由设计

添加views/layout/index.vue作为首页:

<template>
    首页
</template>

依次添加:

  • views/login/index.vue,登录页
  • views/home/index.vue,Home页
  • views/category/index.vue,分类页

eslint 会报错,提示文件命名不符合标准,可以修改.eslintrc.cjs关闭报错:

module.exports = {
   
  // ...
  rules: {
   
    'vue/multi-word-component-names': "off"
  }
}

修改路由配置router/index.js

import {
    createRouter, createWebHistory } from 'vue-router'
import LayoutVue from '@/views/layout/index.vue'
import LoginVue from '@/views/login/index.vue'
import HomeVue from '@/views/home/index.vue'
import CategoryVue from '@/views/category/index.vue'

const router = createRouter({
   
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
   
      path: '/',
      component: LayoutVue,
      children: [
        {
    path: '', component: HomeVue },
        {
    path: '/category', component: CategoryVue }
      ]
    },
    {
    path: '/login', component: LoginVue }
  ]
})

export default router

值得注意的是,代表 Home 页的子路由 path 设置为空字符串,这样可以让/路径默认展示 Home 页。

修改App.vue,添加路由出口:

<template>
  <RouterView />
</template>

修改views/layout/index.vue,添加路由出口:

<template>
    首页
    <RouterView />
</template>

现在项目的路由是:

  • /,Home 页
  • /category,分类页
  • /login,登录页

引入静态资源和样式

将图片相关资源 images 添加到assets目录下,将样式文件common.scss添加到styles目录下。

修改 main.js,导入样式:

// import './assets/main.css'
import '@/styles/common.scss'

为了方便查看错误提示信息,可以添加插件:

image-20240111120319630

sass 自动导入

添加一个存放颜色相关变量的 sass 文件styles/var.scss

$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;

修改 vite.config.js

css: {
   
    preprocessorOptions: {
   
      scss: {
   
        // 自动导入scss文件
        additionalData: `
          @use "@/styles/element/index.scss" as *;
          @use "@/styles/var.scss" as *;
        `,
      }
    }
}

测试,修改App.vue

<template>
  <div class="test">Hello World!</div>
  <RouterView />
</template>
<style scoped lang="scss">
.test{
  color: $helpColor;
}
</style>

Layout

页面搭建

vies/layout中添加以下视图:LayoutNav.vueLayoutHeader.vueLayoutFooter.vue

修改views/layout/index.vue,使用这些视图填充页面:

<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
</script>

<template>
  <LayoutNav />
  <LayoutHeader />
  <RouterView />
  <LayoutFooter />
</template>

字体图标引入

修改根目录下的index.html,添加:

  <link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">

这里使用的是阿里的素材库,具体的使用方式可以参考这个视频

一级导航渲染

封装接口调用,添加apis/layout.js

import http from "../utils/http";

export const getCategorysService =  ()=>{
   
    return http.get('/home/category/head')
}

调用接口,将返回值填充进响应式数据,用响应式数据完成页面渲染。

修改LayoutHeader.vue

<script setup>
import { getCategorysService } from "@/apis/layout.js";
import {ref} from 'vue'
const categorys = ref([])
const getCategorys = async ()=>{
    const result = await getCategorysService()
    categorys.value = result.result
}
getCategorys()
</script>
<template>
	<li v-for="cat in categorys" :key="cat.id"> <RouterLink to="/">{
  { cat.name }}</RouterLink> </li>
</template>

吸顶导航栏

添加views/layout/component/LayoutFixed.vue

views/layout/index.vue中引入:

<script setup>
import LayoutNav from './components/LayoutNav.vue'
import LayoutHeader from './components/LayoutHeader.vue'
import LayoutFooter from './components/LayoutFooter.vue'
import LayoutFixed from '@/views/layout/components/LayoutFixed.vue'
</script>

<template>
    <LayoutFixed />
    <LayoutNav />
    <LayoutHeader />
    <RouterView />
    <LayoutFooter />
</template>

吸顶导航栏中,用show类别控制是否显示:

<div class="app-header-sticky show">

需要知道鼠标在y轴的滚动距离,这里用一个函数库 vueuse 获取。

安装:

npm i @vueuse/core

使用函数获取滚动距离:

<script setup>
import { useWindowScroll } from '@vueuse/core'

const { y } = useWindowScroll()
</script>
<div class="app-header-sticky" :class="{ show: y > 78 }">

Pinia 优化重复请求

吸顶导航与普通的导航栏使用相同的商品分类数据,为了避免重复请求接口,可以使用 Pinia 存储数据。

创建分类的数据存储:

import {
    defineStore } from 'pinia'
import {
    ref } from 'vue'
import {
    getCategorysService } from '@/apis/layout'
export const useCategoryStore = defineStore('category', () => {
   
    const categorys = ref([])
    const loadCategorys = async () => {
   
        const result = await getCategorysService()
        categorys.value = result.result
    }
    return {
    categorys, loadCategorys }
})

在吸顶导航和普通导航共同的父组件layout/index.vue中触发 Store 的 action 以加载分类数据:

<script setup>
// ...
import { useCategoryStore } from '@/stores/category'
const categoryStore = useCategoryStore()
categoryStore.loadCategorys()
</script>

在固定导航栏中使用数据填充导航栏:

<script setup>
import { useWindowScroll } from '@vueuse/core'
import {useCategoryStore} from '@/stores/category'
const categoryStore = useCategoryStore()
const { y } = useWindowScroll()
</script>

<template>
    <div class="app-header-sticky" :class="{ show: y > 78 }">
        <div class="container">
            <RouterLink class="logo" to="/" />
            <!-- 导航区域 -->
            <ul class="app-header-nav ">
                <li class="home">
                    <RouterLink to="/">首页</RouterLink>
                </li>
                <li v-for="cat in categoryStore.categorys" :key="cat.id">
                    <RouterLink to="/">{
  { cat.name }}</RouterLink>
                </li>
            </ul>

            <div class="right">
                <RouterLink to="/">品牌</RouterLink>
                <RouterLink to="/">专题</RouterLink>
            </div>
        </div>
    </div>
</template>

普通导航栏中的使用方式是相同的,这里不再赘述。

Home

整体结构拆分

将 Home 页拆分成以下几部分:

<script setup>
import HomeBannerVue from './components/HomeBanner.vue'
import HomeCategoryVue from './components/HomeCategory.vue'
import HomeHotVue from './components/HomeHot.vue'
import HomeNewVue from './components/HomeNew.vue'
import HomeProductVue from './components/HomeProduct.vue'
</script>
<template>
    <div class="container">
        <HomeCategoryVue />
        <HomeBannerVue />
    </div>
    <HomeNewVue />
    <HomeHotVue />
    <HomeProductVue />
</template>

分类

分类组件的基本实现见这里

所依赖的数据可以从 Pinia 中的分类信息获取:

<script setup>
import { useCategoryStore } from '@/stores/category'
const categoryStore = useCategoryStore()
</script>

<template>
    <div class="home-category">
        <ul class="menu">
            <li v-for="cat in categoryStore.categorys" :key="cat.id">
                <RouterLink to="/">{
  { cat.name }}</RouterLink>
                <RouterLink v-for="child in cat.children.slice(0, 2)" :key="child.id" to="/">{
  { child.name }}</RouterLink>
                <!-- 弹层layer位置 -->
                <div class="layer">
                    <h4>分类推荐 <small>根据您的购买或浏览记录推荐</small></h4>
                    <ul>
                        <li v-for="good in cat.goods" :key="good.id">
                            <RouterLink to="/">
                                <img alt="" :src="good.picture"/>
                                <div class="info">
                                    <p class="name ellipsis-2">
                                        {
  { good.name }}
                                    </p>
                                    <p class="desc ellipsis">{
  { good.desc }}</p>
                                    <p class="price"><i>¥</i>{
  { good.price }}</p>
                                </div>
                            </RouterLink>
                        </li>
                    </ul>
                </div>
            </li>
        </ul>
    </div>
</template>

轮播图

基本实现代码可以从这里获取。

封装接口:

import http from '@/utils/http'

export const getHomeBannerService = ()=>{
   
    return http.get('/home/banner')
}

加载数据:

<script setup>
import { getHomeBannerService } from '@/apis/home';
import { ref } from 'vue';
const banner = ref([])
const loadHomeBanner = async ()=>{
    const result = await getHomeBannerService()
    banner.value = result.result
}
loadHomeBanner()
</script>



<template>
  <div class="home-banner">
    <el-carousel height="500px">
      <el-carousel-item v-for="item in banner" :key="item.id">
        <img :src="item.imgUrl" alt="">
      </el-carousel-item>
    </el-carousel>
  </div>
</template>

面板组件封装

面板组件HomePannel.vue的基本实现可以从这里获取。

将简单信息封装成 props(属性),将复杂信息封装成 slot(插槽):

<script setup>
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></slot>
        </div>
    </div>
</template>

测试:

<HomePannelVue title="新鲜好物" subTitle="更多商品">
    新鲜好物
</HomePannelVue>
<HomePannelVue title="热销商品" subTitle="更多商品">
    热销商品
</HomePannelVue>

新鲜好物

新鲜好物页面HomeNew.vue的基本实现见这里

封装接口:

//新鲜好物
export const getNewService = ()=>{
   
    return http.get('/home/new')
}

从接口获取数据渲染页面:

<script setup>
import { getNewService } from '@/apis/home'
import { ref } from 'vue'
import HomePannelVue from './HomePannel.vue';
const newGoods = ref([])
const loadNewGoods = async () => {
    const result = await getNewService()
    newGoods.value = result.result
}
loadNewGoods()
</script>

<template>
    <HomePannelVue title="新鲜好物" subTitle="新鲜出炉 品质靠谱">
        <ul class="goods-list">
            <li v-for="good in newGoods" :key="good.id">
                <RouterLink to="/">
                    <img :src="good.picture" alt="" />
                    <p class="name">{
  { good.name }}</p>
                    <p class="price">&yen;{
  { good.price }}</p>
                </RouterLink>
            </li>
        </ul>
    </HomePannelVue>
</template>

图片懒加载

需要实现一个自定义指令v-img-lazy

修改main.js

// import './assets/main.css'
import '@/styles/common.scss'

import {
    createApp } from 'vue'
import {
    createPinia } from 'pinia'
import {
    useIntersectionObserver } from '@vueuse/core'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)

app.directive('img-lazy', {
   
    mounted(el, binding) {
   
        //el,指令绑定的对象
        //binding.value,指令 = 后的表达式的值
        console.log(el, binding.value)
        useIntersectionObserver(
            el,
            ([{
     isIntersecting }]) => {
   
                if (isIntersecting) {
   
                    el.src = binding.value
                }
            },
        )
    },
})
app.mount('#app')

插件封装

在入口文件中写入懒加载逻辑是不合适的,应当封装成插件。

创建插件文件directives/img-lazy.js

import {
    useIntersectionObserver } from '@vueuse/core'
//图片懒加载插件
export const imgLazyPlugin = {
   
  install(app) {
   
    // 配置此应用
    app.directive('img-lazy', {
   
      mounted(el, binding) {
   
        //el,指令绑定的对象
        //binding.value,指令 = 后的表达式的值
        console.log(el, binding.value)
        useIntersectionObserver(
          el,
          ([{
     isIntersecting }]) => {
   
            if (isIntersecting) {
   
              el.src = binding.value
            }
          },
        )
      },
    })
  }
}

这里的useIntersectionObserver函数是 vueuse 库中用于监听某个控件是否在 Window 中显示的函数。

修改main.js,使用插件:

// import './assets/main.css'
import '@/styles/common.scss'

import {
    createApp } from 'vue'
import {
    createPinia } from 'pinia'
import {
    imgLazyPlugin } from './directives/img-lazy'

import App from './App.vue'
import router from './router'

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(imgLazyPlugin)

app.mount('#app')

避免重复监听

如果不在图片加载后手动停止监听,监听行为就一直存在。

修改img-lazy.js,手动停止监听:

const {
    stop } = useIntersectionObserver(
    el,
    ([{
     isIntersecting }]) => {
   
        if (isIntersecting) {
   
            el.src = binding.value
            stop()
        }
    },
)

useIntersectionObserver会返回一个停止的函数,在合适的时候调用即可。

商品列表

商品列表控件HomeProduct.vue的初始代码可以从这里获取。

封装接口:

export const getGoodsService = ()=>{
   
    return http.get('/home/goods')
}

渲染数据:

<script setup>
import HomePanel from './HomePannel.vue'
import { getGoodsService } from '@/apis/home'
import { ref } from 'vue'
const goodsProduct = ref([])
const loadGoods = async () => {
    const res = await getGoodsService()
    goodsProduct.value = res.result
}
loadGoods()
</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&
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值