文章目录
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状态码错误,需要我们做额外处理
两个需要思考的问题
- 我们能确定用户到底是在访问哪个接口时出现的401错误吗?在什么位置去拦截这个401?
- 检测到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验证,所以只能在商品详情页面测试
- 登录
- 手动修改token
- 在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组件把标签都给我们写好了,我们拿来用就好了