feat(theme): add support <Swiper>

This commit is contained in:
pengzhanbo 2024-09-13 00:07:27 +08:00
parent bde059ac3a
commit 272b921168
3 changed files with 223 additions and 1 deletions

View File

@ -0,0 +1,206 @@
<script setup lang="ts">
import {
Autoplay,
EffectCards,
EffectCoverflow,
EffectCreative,
EffectCube,
EffectFade,
EffectFlip,
Mousewheel,
Navigation,
Pagination,
} from 'swiper/modules'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { computed } from 'vue'
import type { AutoplayOptions, SwiperModule, Swiper as SwiperType } from 'swiper/types'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import 'swiper/css/effect-fade'
import 'swiper/css/effect-cube'
import 'swiper/css/effect-flip'
import 'swiper/css/effect-coverflow'
import 'swiper/css/effect-cards'
import 'swiper/css/effect-creative'
interface SlideItem {
/**
* 图片地址
*/
link: string
/**
* 跳转链接
*/
href?: string
alt?: string
}
interface Props {
items?: (string | SlideItem)[]
width?: number | string // px
height?: number | string // px
mode?: 'banner' | 'carousel' | 'broadcast' // banner: ; carousel: ; broadcast:
navigation?: boolean //
effect?: 'slide' | 'fade' | 'cube' | 'flip' | 'coverflow' | 'cards' | 'creative' //
delay?: number // mode: 'banner' ms
speed?: number // ms
loop?: boolean //
pauseOnMouseEnter?: boolean // mode: 'banner' mode: 'carousel'
swipe?: boolean //
}
const props = withDefaults(defineProps<Props>(), {
width: '100%',
height: '100%',
mode: 'banner',
navigation: true,
effect: 'slide',
delay: 3000,
speed: 300,
loop: true,
pauseOnMouseEnter: false,
swipe: true,
})
const slideList = computed<SlideItem[]>(() => {
return props.items?.map((link) => {
if (typeof link === 'string')
return { link }
return link
}) ?? []
})
function parseSize(size: number | string) {
if (typeof size === 'number') {
return `${size}px`
}
return size
}
const styles = computed(() => ({
width: parseSize(props.width),
height: parseSize(props.height),
}))
const modules = computed<SwiperModule[]>(() => {
if (props.mode === 'carousel')
return [Autoplay]
if (props.mode === 'broadcast')
return [Navigation, Pagination, Mousewheel]
const modules: SwiperModule[] = [Navigation, Pagination, Autoplay]
const effectMoudles = {
fade: EffectFade,
cube: EffectCube,
flip: EffectFlip,
coverflow: EffectCoverflow,
cards: EffectCards,
creative: EffectCreative,
}
if (props.effect !== 'slide') {
modules.push(effectMoudles[props.effect])
}
return modules
})
const autoplay = computed<AutoplayOptions | boolean>(() => {
if (props.mode === 'banner') {
return {
delay: props.delay,
disableOnInteraction: false, // swiper autoplay true
pauseOnMouseEnter: props.pauseOnMouseEnter, // swiper false
}
}
else if (props.mode === 'carousel') {
return {
delay: 0,
disableOnInteraction: false,
}
}
return false
})
const hasNavigation = computed(() =>
props.mode === 'banner' || props.mode === 'broadcast' ? props.navigation : false,
)
function onSwiper(swiper: SwiperType) {
if (props.mode === 'carousel' && props.pauseOnMouseEnter) {
swiper.el.onmouseenter = () => swiper.autoplay.stop()
swiper.el.onmouseleave = () => swiper.autoplay.start()
}
}
</script>
<template>
<ClientOnly>
<Swiper
class="vp-swiper"
:class="{ 'swiper-no-swiping': mode === 'banner' ? !swipe : mode === 'carousel' }"
:style="styles"
:modules="modules"
:autoplay="autoplay"
:navigation="hasNavigation"
:pagination="props.mode !== 'carousel' ? {
dynamicBullets: true,
clickable: true,
} : false"
:speed="speed"
:loop="loop"
:effect="mode === 'banner' ? effect : 'slide'"
lazy
v-bind="$attrs"
@swiper="onSwiper"
>
<SwiperSlide v-for="(item, index) in slideList" :key="item.link + index">
<a v-if="item.href" :href="item.href" target="_blank" rel="noopener noreferrer" class="swiper-slide-link">
<img class="swiper-slide-img" :src="item.link" :alt="item.alt" loading="lazy">
</a>
<img v-else class="swiper-slide-img" :src="item.link" :alt="item.alt" loading="lazy">
</SwiperSlide>
</Swiper>
</ClientOnly>
</template>
<style>
.vp-swiper {
margin: 24px 0;
}
.swiper-slide-link {
display: block;
height: 100%;
}
.swiper-slide-img {
width: 100%;
height: 100%;
cursor: default !important;
object-fit: cover;
}
.swiper-slide-link .swiper-slide-img {
cursor: pointer !important;
}
.swiper {
--swiper-theme-color: var(--vp-c-bg);
--swiper-pagination-bullet-inactive-color: var(--vp-c-bg);
--swiper-pagination-bullet-inactive-opacity: 0.4;
}
.swiper-wrapper {
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
.swiper-pagination-bullet {
width: 12px;
height: 12px;
}
</style>

View File

@ -65,3 +65,13 @@ declare module '@internal/iconify' {
icons,
}
}
declare module 'swiper/css' {
const res: any
export default res
}
declare module 'swiper/css/*' {
const res: any
export default res
}

View File

@ -5,12 +5,13 @@ import {
addViteSsrNoExternal,
chainWebpack,
} from '@vuepress/helper'
import { isPackageExists } from 'local-pkg'
import type { App } from 'vuepress'
export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
addViteConfig(bundlerOptions, app, {
build: {
chunkSizeWarningLimit: 1024,
chunkSizeWarningLimit: 2048,
},
})
@ -23,6 +24,11 @@ export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
'@vuepress/plugin-watermark',
])
if (isPackageExists('swiper')) {
addViteOptimizeDepsInclude(bundlerOptions, app, 'swiper', true)
addViteSsrNoExternal(bundlerOptions, app, ['swiper'])
}
chainWebpack(bundlerOptions, app, (config) => {
config.module
.rule('scss')