目录
- 轮播图功能-获取数据
- 轮播图-通用轮播图组件
- 轮播图-数据渲染
- 轮播图-逻辑封装
轮播图功能-获取数据
目标: 基于pinia获取轮播图数据
核心代码:
(1)在types/data.d.ts
文件中定义轮播图数据的类型声明
// 所有接口的通用类型 | |
export type ApiRes <T> = { | |
code: string, | |
msg: string, | |
result: T | |
} | |
// 轮播图类型 | |
export type BannerItem = { | |
hrefUrl: string | |
id: string | |
imgUrl: string | |
type: string | |
} |
(2)在store/home.ts
文件中封装接口,用于获取轮播图数据
import { ApiRes, BannerItem } from '@/types/data' | |
import request from '@/utils/request' | |
import { defineStore } from 'pinia' | |
export default defineStore('home', { | |
state: () => ({ | |
bannerList: [] as BannerItem[], | |
}), | |
actions: { | |
async getBannerList() { | |
const {data: res} = await request.get<ApiRes<BannerItem[]>>('/home/banner') | |
this.bannerList = res.result | |
}, | |
}, | |
}) |
(3)在store/index.ts
中导入
import useCategoryStore from './modules/category' | |
import useHomeStore from './modules/home' | |
export default function useStore() { | |
return { | |
category: useCategoryStore(), | |
home: useHomeStore(), | |
} | |
} |
(4)通过开发者工具查看数据
<script lang="ts" setup> | |
import useStore from '@/store' | |
const { home } = useStore() | |
home.getBannerList() | |
</script> |
轮播图-通用轮播图组件
项目中会多次使用到轮播图组件,但是轮播图渲染的数据是不一样的。
但是轮播图的基本功能都是一样的,比如图片切换,自动播放等等。
因此需要封装一个通用的轮播图组件。
(1)通用轮播图的基本结构src/components/carousel/index.vue
fade 类:用于控制图片的显示和隐藏
active 类:用于控制小圆点高亮
<script lang="ts" setup name="Carousel"> | |
defineProps() | |
</script> | |
<template> | |
<div class="carousel"> | |
<ul class="carousel-body"> | |
<li class="carousel-item fade"> | |
<RouterLink to="/"> | |
<img | |
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg" | |
alt="" | |
/> | |
</RouterLink> | |
</li> | |
<li class="carousel-item"> | |
<RouterLink to="/"> | |
<img | |
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg" | |
alt="" | |
/> | |
</RouterLink> | |
</li> | |
<li class="carousel-item"> | |
<RouterLink to="/"> | |
<img | |
src="http://yjy-xiaotuxian-dev.oss-cn-beijing.aliyuncs.com/picture/2021-04-15/1ba86bcc-ae71-42a3-bc3e-37b662f7f07e.jpg" | |
alt="" | |
/> | |
</RouterLink> | |
</li> | |
</ul> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev" | |
><i class="iconfont icon-angle-left"></i | |
></a> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next" | |
><i class="iconfont icon-angle-right"></i | |
></a> | |
<div class="carousel-indicator"> | |
<span class="active"></span> | |
<span></span> | |
<span></span> | |
<span></span> | |
<span></span> | |
</div> | |
</div> | |
</template> | |
<style scoped lang="less"> | |
.xtxcarousel { | |
width: 100%; | |
height: 100%; | |
min-width: 300px; | |
min-height: 150px; | |
position: relative; | |
.carousel { | |
&-body { | |
width: 100%; | |
height: 100%; | |
} | |
&-item { | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
left: 0; | |
top: 0; | |
opacity: 0; | |
transition: opacity 0.5s linear; | |
&.fade { | |
opacity: 1; | |
z-index: 1; | |
} | |
img { | |
width: 100%; | |
height: 100%; | |
} | |
} | |
&-indicator { | |
position: absolute; | |
left: 0; | |
bottom: 20px; | |
z-index: 2; | |
width: 100%; | |
text-align: center; | |
span { | |
display: inline-block; | |
width: 12px; | |
height: 12px; | |
background: rgba(0, 0, 0, 0.2); | |
border-radius: 50%; | |
cursor: pointer; | |
~ span { | |
margin-left: 12px; | |
} | |
&.active { | |
background: #fff; | |
} | |
} | |
} | |
&-btn { | |
width: 44px; | |
height: 44px; | |
background: rgba(0, 0, 0, 0.2); | |
color: #fff; | |
border-radius: 50%; | |
position: absolute; | |
top: 228px; | |
z-index: 2; | |
text-align: center; | |
line-height: 44px; | |
opacity: 0; | |
transition: all 0.5s; | |
&.prev { | |
left: 20px; | |
} | |
&.next { | |
right: 20px; | |
} | |
} | |
} | |
&:hover { | |
.carousel-btn { | |
opacity: 1; | |
} | |
} | |
} | |
</style> |
(2)全局注册通用轮播图 src/components/index.ts
import type { App } from 'vue' | |
import skelecton from './skeleton/index.vue' | |
+import Carousel from './carousel/index.vue' | |
export default { | |
install(app: App) { | |
app.component(skelecton.name, skelecton) | |
+ app.component(Carousel.name, Carousel) | |
}, | |
} |
(3)在广告组件中使用src/views/home/components/home-banner.vue
<template> | |
<div class="home-banner"> | |
<!-- 轮播图 --> | |
<Carousel></XtxCarousel> | |
</div> | |
</template> |
(4)覆盖样式,控制箭头和小圆点的位置src/views/home/components/home-banner.vue
:deep(.carousel-btn.prev) { | |
left: 270px ; | |
} | |
:deep(.carousel-indicator) { | |
padding-left: 250px; | |
} |
(5)查看效果
轮播图-数据渲染
目的
home-banner组件把数据传递给Carousel组件进行渲染
(1)父传子的方式将数据传给通用轮播图组件src/views/home/components/home-banner.vue
<Carousel :slides="home.bannerList"></Carousel>
(2)子组件接收数据src/components/carousel/index.vue
了解写法:如果通过js的方法定义类型,需要单独引入PropType进行编写
<script lang="ts" setup name="Carousel"> | |
import { BannerItem } from '@/types/data' | |
// import { PropType } from 'vue' | |
// defineProps({ | |
// slides: { | |
// type: Array as PropType<BannerItem[]>, | |
// required: true, | |
// }, | |
// }) | |
defineProps<{ | |
slides: BannerItem[] | |
}>() | |
</script> |
(3)渲染轮播图数据src/components/carousel/index.vue
<template> | |
<div class="carousel"> | |
<ul class="carousel-body"> | |
<li class="carousel-item fade" v-for="item in slides" :key="item.id"> | |
<RouterLink :to="item.hrefUrl"> | |
<img :src="item.imgUrl" alt="" /> | |
</RouterLink> | |
</li> | |
</ul> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev"> | |
<i class="iconfont icon-angle-left"></i> | |
</a> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next"> | |
<i class="iconfont icon-angle-right"></i> | |
</a> | |
<div class="carousel-indicator"> | |
<span v-for="item in slides" :key="item.id" class="active"></span> | |
</div> | |
</div> | |
</template> |
(4)控制高亮的下标
<script lang="ts" setup name="Carousel"> | |
const active = ref(0) | |
</script> |
(5)高亮渲染
- 添加的fade的图片才会展示,所以根据当前索引号进行判断,索引号等于active的才进行展示
- 添加了active类名的小圆点才会高亮,高亮逻辑跟图片一致
<template> | |
<div class="carousel"> | |
<ul class="carousel-body"> | |
<li | |
class="carousel-item" | |
+ :class="{ fade: active === index }" | |
+ v-for="(item, index) in slides" | |
:key="item.id" | |
> | |
<RouterLink :to="item.hrefUrl"> | |
<img :src="item.imgUrl" alt="" /> | |
</RouterLink> | |
</li> | |
</ul> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev"> | |
<i class="iconfont icon-angle-left"></i> | |
</a> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next"> | |
<i class="iconfont icon-angle-right"></i> | |
</a> | |
<div class="carousel-indicator"> | |
<span | |
+ v-for="(item, index) in slides" | |
:key="item.id" | |
+ :class="{ active: active === index }" | |
></span> | |
</div> | |
</div> | |
</template> |
轮播图-逻辑封装
实现需求:
轮播图里面的图片需要从父组件传入(因为轮播组件可以复用)
父组件需要控制轮播图的是否自动播放、动画时间(处理默认值逻辑)
是否自动播放和动画时间都是需要默认值的(如果不传就可以使用轮播组件自己提供的默认值)
播放逻辑
- 点击小圆点可以切换图片
- 点击prev和next按钮可以播放指定图片(根据图片个数判断播放的循环)
- 如果父组件配置了自动播放,则需要定时播放图片
- 鼠标进入轮播图,暂停轮播
- 鼠标离开轮播图,继续轮播
- 注意点:组件卸载的时候需要清除定时轮播效果(不然组件重新加载的时候会导致多个定时器开启)
(1)父组件传值给轮播图src/views/home/components/home-banner.vue
<template> | |
<div class="home-banner"> | |
<!-- 轮播图 --> | |
<Carousel :slides="slides" autoPlay :duration="3000"></XtxCarousel> | |
</div> | |
</template> |
(2)props接收src/components/Carousel.vue
<script lang="ts" setup name="Carousel"> | |
import { BannerItem } from '@/types/data' | |
import { ref, PropType } from 'vue' | |
defineProps({ | |
slides: { | |
type: Array as PropType<BannerItem[]>, | |
required: true, | |
}, | |
autoPlay: { | |
type: Boolean, | |
default: false, | |
}, | |
duration: { | |
type: Number, | |
default: 3000, | |
}, | |
}) | |
const active = ref(0) | |
</script> |
(3)轮播图的播放逻辑
<script lang="ts" setup name="Carousel"> | |
import { BannerItem } from '@/types/data' | |
import { onMounted, onUnmounted, PropType, ref } from 'vue' | |
// import { PropType } from 'vue' | |
const props = defineProps({ | |
slides: { | |
type: Array as PropType<BannerItem[]>, | |
required: true, | |
}, | |
duration: { | |
type: Number, | |
default: 3000, | |
}, | |
autoPlay: { | |
type: Boolean, | |
default: false, | |
}, | |
}) | |
// const props = defineProps<{ | |
// slides: BannerItem[] | |
// }>() | |
// 控制高亮 | |
const active = ref(0) | |
const prev = () => { | |
if (active.value <= 0) { | |
active.value = props.slides.length - 1 | |
} else { | |
active.value-- | |
} | |
} | |
const next = () => { | |
if (active.value >= props.slides.length - 1) { | |
active.value = 0 | |
} else { | |
active.value++ | |
} | |
} | |
const play = () => { | |
// 如果没有自动播放 | |
if (!props.autoPlay) return | |
// 在ts中,使用定时器,window.setInterval | |
timer = window.setInterval(() => { | |
next() | |
}, props.duration) | |
} | |
const stop = () => { | |
clearInterval(timer) | |
} | |
let timer = -1 | |
// 自动播放 | |
onMounted(() => { | |
play() | |
}) | |
onUnmounted(() => { | |
stop() | |
}) | |
</script> |
(4)鼠标进入和离开操作
<div class="carousel" @mouseenter="stop" @mouseleave="play">
(5)鼠标经过小圆点切换
<span | |
v-for="(item, index) in slides" | |
:key="item.id" | |
:class="{ active: active === index }" | |
@mouseenter="active = index" | |
></span> |
(6)点击左右箭头切换
const prev = () => { | |
if (active.value === 0) { | |
active.value = props.slides.length - 1 | |
} else { | |
active.value-- | |
} | |
} | |
const next = () => { | |
if (active.value === props.slides.length - 1) { | |
active.value = 0 | |
} else { | |
active.value++ | |
} | |
} | |
// 注册事件 | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev" ="prev"> | |
<i class="iconfont icon-angle-left"></i> | |
</a> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next" @click="next"> | |
<i class="iconfont icon-angle-right"></i> | |
</a> | |
vascript | |
const prev = () => { | |
if (active.value === 0) { | |
active.value = props.slides.length - 1 | |
} else { | |
active.value-- | |
} | |
} | |
const next = () => { | |
if (active.value === props.slides.length - 1) { | |
active.value = 0 | |
} else { | |
active.value++ | |
} | |
} | |
// 注册事件 | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn prev" ="prev"> | |
<i class="iconfont icon-angle-left"></i> | |
</a> | |
<a href="javascript:;" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" class="carousel-btn next" @click="next"> | |
<i class="iconfont icon-angle-right"></i> | |
</a> |