Vue3项目技巧(更新中ing)

axios封装

http.js

在这里插入图片描述

//axios基础的封装
import axios from 'axios'

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

//拦截器
// 添加请求拦截器
httpInstance.interceptors.request.use(function (config) {
    return config;
}, function (error) {
    return Promise.reject(error);
});

// 添加响应拦截器
httpInstance.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    return Promise.reject(error);
});

export default httpInstance

testAPI.js

在这里插入图片描述

import httpInstance from "@/utils/http";

export function getCategory(){
   return httpInstance({
        url:'home/category/head'
    })
}

main.js测试

在这里插入图片描述

//测试接口函数
import {getCategory} from "@/apis/testAPI";
getCategory().then (res => {
    console.log(res)
})

如果项目中需要多个baseURL

在这里插入图片描述

自动导入scss文件

在这里插入图片描述

案例文件

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

使用案例

<!--setup-开关:允许在script书写组合式API-->
<script setup>

</script>

<template>
  <div>
    <el-button type="primary">Primary</el-button>
    <!--一级路由出口-->
    <RouterView />
    <div class="test">
      test scss
    </div>
  </div>
</template>

<style scoped lang="scss">
.test{
  color:$warnColor;
}
</style>

引入aliyun图标库

先看效果

在这里插入图片描述

查看官网文档

官方文档说明

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

引入并使用

如果没有index.html,新建一个就好了

注意需要替换内容

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vite App</title>
    <link rel="stylesheet" href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css">
</head>

<body>
<div id="app">
</div>
<script type="module" src="/src/main.js"></script>
</body>

</html>

使用

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

vueuse实现-吸附导航交互

安装

npm i @vueuse/core

案例

这个案例就是又生成了一个新的组件,根据一定情况渲染在页面中
具体来说,当滚动位置y大于78时,该元素会添加show类。

在CSS部分,有一个名为.show的CSS类定义。这个类用于在app-header-sticky元素具有show类时应用样式。这些样式包括过渡效果(transition)为0.3秒线性过渡,变换(transform)为默认值,以及不透明度(opacity)为1。

因此,根据滚动位置y是否大于78,app-header-sticky元素将动态地添加或移除show类,从而触发过渡效果和样式的变化。

核心代码

在这里插入图片描述

<script setup>
// vueUse
import { useScroll } from '@vueuse/core'
const { y } = useScroll(window)
</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>
          <RouterLink to="/">居家</RouterLink>
        </li>
        <li>
          <RouterLink to="/">美食</RouterLink>
        </li>
        <li>
          <RouterLink to="/">服饰</RouterLink>
        </li>
        <li>
          <RouterLink to="/">母婴</RouterLink>
        </li>
        <li>
          <RouterLink to="/">个护</RouterLink>
        </li>
        <li>
          <RouterLink to="/">严选</RouterLink>
        </li>
        <li>
          <RouterLink to="/">数码</RouterLink>
        </li>
        <li>
          <RouterLink to="/">运动</RouterLink>
        </li>
        <li>
          <RouterLink to="/">杂项</RouterLink>
        </li>
      </ul>

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


<style scoped lang='scss'>
.app-header-sticky {
  width: 100%;
  height: 80px;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  background-color: #fff;
  border-bottom: 1px solid #e4e4e4;
  // 此处为关键样式!!!
  // 状态一:往上平移自身高度 + 完全透明
  transform: translateY(-100%);
  opacity: 0;

  // 状态二:移除平移 + 完全不透明
  &.show {
    transition: all 0.3s linear;
    transform: none;
    opacity: 1;
  }

  .container {
    display: flex;
    align-items: center;
  }

  .logo {
    width: 200px;
    height: 80px;
    background: url("@/assets/images/logo.png") no-repeat right 2px;
    background-size: 160px auto;
  }

  .right {
    width: 220px;
    display: flex;
    text-align: center;
    padding-left: 40px;
    border-left: 2px solid $xtxColor;

    a {
      width: 38px;
      margin-right: 40px;
      font-size: 16px;
      line-height: 1;

      &:hover {
        color: $xtxColor;
      }
    }
  }
}

.app-header-nav {
  width: 820px;
  display: flex;
  padding-left: 40px;
  position: relative;
  z-index: 998;

  li {
    margin-right: 40px;
    width: 38px;
    text-align: center;

    a {
      font-size: 16px;
      line-height: 32px;
      height: 32px;
      display: inline-block;

      &:hover {
        color: $xtxColor;
        border-bottom: 1px solid $xtxColor;
      }
    }

    .active {
      color: $xtxColor;
      border-bottom: 1px solid $xtxColor;
    }
  }
}
</style>

多个组件共享的请求、数据、封装到pinia

案例

封装以下请求

import { ref } from 'vue'
import { defineStore } from 'pinia'
import { getCategoryAPI } from '@/apis/layout'
export const useCategoryStore = defineStore('category', () => {
    // 导航列表的数据管理
    // state 导航列表数据
    const categoryList = ref([])

    // action 获取导航数据的方法
    const getCategory = async () => {
        const res = await getCategoryAPI()
        categoryList.value = res.data.result
    }

    return {
        categoryList,
        getCategory
    }
})

父组件中调用

//触发获取导航列表的action
import {useCategoryStore} from "@/stores/categoryStore";
import {onMounted} from "vue";

const categoryStore = useCategoryStore();

onMounted(() => categoryStore.getCategory())

子组件中应用

使用变更数据后的state

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


<template>
  <ul class="app-header-nav">
    <li class="home">
      <RouterLink to="/">首页</RouterLink>
    </li>
    <li class="home" v-for="item in categoryStore.categoryList" :key="item.id">
      <RouterLink active-class="active" :to="`/category/${item.id}`">
        {{ item.name }}
      </RouterLink>
    </li>
  </ul>
</template>

资源懒加载案例(vue3自定义指令&vueuse)

案例模拟

main.js

import { useIntersectionObserver } from '@vueuse/core'
//定义全局指令
app.directive('img-lazy',{
    mounted(el,binding){
        //el:指令绑定的那个元素 img
        // binding: binding.value 指令等于号后面绑定的表达式的值 图片url
        console.log(el,binding.value)

        useIntersectionObserver(
            el,
            ([{ isIntersecting }]) => {
                console.log(isIntersecting)
                if(isIntersecting){
                    //进入视口区域
                    el.src = binding.value
                }
            },

        )
    }
})

组件

核心内容

在这里插入图片描述

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

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

</script>

<template>
  <HomePanel title="人气推荐" sub-title="人气爆款 不容错过">
    <template #main>
      <ul class="goods-list">
        <li v-for="item in hotList" :key="item.id">
          <RouterLink to="/">
<!--            <img :src="item.picture" alt="">-->
            <img v-img-lazy="item.picture" alt="">
            <p class="name">{{ item.title }}</p>
            <p class="desc">{{ item.alt }}</p>
          </RouterLink>
        </li>
      </ul>
    </template>
  </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>

懒加载模块优化

分离文件
在这里插入图片描述

// 定义懒加载插件

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()
                        }
                    },
                )
            }
        })
    }
}

main.js

在这里插入图片描述

懒加载代码优化

在这里插入图片描述

// 定义懒加载插件

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()
                        }
                    },
                )
            }
        })
    }
}

效果

加载后不再重新加载,节省资源
在这里插入图片描述

路由url发生改变时,强制对当前路由销毁重建,可用于一级或二级路由

使用key的方法(简单好用)

  <RouterView :key="$route.fullPath"/>

在这里插入图片描述

效果演示

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

onBeforeRouteUpdate导航钩子

  • 每次路由更新之前执行

示例代码

    onBeforeRouteUpdate(
        (to) => {
            //to代表目标对象
            console.log(to)
            getCategory(to.params.id)
        })

我们原来的方法要重新接收参数

    //id是默认参数
    const getCategory = async (id = route.params.id) => {
        const res = await getTopCategoryAPI(id)
        categoryData.value = res.data.result
    }

网页拖动到底部、无线加载数据资源

使用element plus的一个属性

v-infinite-scoll

在这里插入图片描述

      <div class="body" v-infinite-scroll="load" :infinite-scroll-disabled="disabled">
        <!-- 商品列表-->
        <goods-item v-for="goods in goodList" :goods="goods" :key="goods.id"></goods-item>
      </div>

事件案例

// 加载更多
const disabled = ref(false)
const load = async () => {
  console.log('加载更多数据咯')
  // 获取下一页的数据
  reqData.value.page++
  const res = await getSubCategoryAPI(reqData.value)
  goodList.value = [...goodList.value, ...res.data.result.items]
  // 加载完毕 停止监听
  if (res.result.items.length === 0) {
    disabled.value = true
  }
}

效果演示

在这里插入图片描述

路由切换时使用户视图跳到顶部

编辑router.js

    scrollBehavior(){
        return {top:0}
    }

在这里插入图片描述

效果查看

在这里插入图片描述

第三方组件使用

Sku组件案例

Sku组件概念

存货单位(英语:stock keeping unit,SKU/ˌɛsˌkeɪˈjuː/),也翻译为库存单元,是一个会计学名词,定义为库存管理
中的最小可用单元,例如纺织品中一个SKU通常表示规格、颜色、款式,而在连锁零售门店中有时称单品为一个SKU
在这里插入图片描述

SKU组件的作用:产出当前用户选择的商品规格,为加入购物车操作提供数据信息

组件使用技巧

问:在实际工作中,经常会遇到别人写好的组件,熟悉一个三方组件,首先重点看什么?
答:props和emit,props决定了当前组件接收什么数据,emit决定了会产出什么数据

在这里插入图片描述

token时效性处理

遇到问题

token的有效性可以保持一定时间,如果用户一段时间不作任何操作,token就会失效,使用失效的token再去请求一些接口,接口就会报401状态码错误,需要我们做额外处理

两个需要思考的问题

  1. 我们能确定用户到底是在访问哪个接口时出现的401错误吗?在什么位置去拦截这个401?
  2. 检测到401之后又该干什么呢?

解决方案

在axios响应拦截器做统一处理

  • 失败回调中拦截401
  • 清除掉过期的用户信息
  • 跳转到登录页

测试token失效效果,手动修改token

在这里插入图片描述

刷新页面
在这里插入图片描述
查看控制台401
在这里插入图片描述

响应拦截器处理401

在这里插入图片描述

httpInstance.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    ElMessage({
        type:'warning',
        message:error.response.data.msg
    })
    // 401 token失效处理
    // 1.清除本地用户数据
    // 2.跳转到登录页
    if(error.response.status === 401){
        const userStore = useUserStore()
        //调用action来变更states,清除用户数据
        userStore.clearUserInfo()
        //跳转到登录页
        router.push('/login')
    }
    return Promise.reject(error);
});

测试

还是上面的步骤,不过这里后台应该只在detail的goods路径做了token验证,所以只能在商品详情页面测试
在这里插入图片描述

  1. 登录
  2. 手动修改token
  3. 在http://localhost:5173/detail/4020262路径刷新页面

分页效果实现[el-pagination标签]

渲染

随便来一个变量,比如total

const total = ref(1817)
 <el-pagination :total="total" background layout="prev, pager, next"/>

效果

为何是182页?因为page默认设置了每页10条数据
在这里插入图片描述

每页2条数据

在这里插入图片描述

点击页数跳转

在这里插入图片描述

总结

我们只是写逻辑即可,elementUI组件把标签都给我们写好了,我们拿来用就好了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼鬼骑士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值