web-home
This commit is contained in:
267
schoolNewsWeb/src/components/base/Carousel.vue
Normal file
267
schoolNewsWeb/src/components/base/Carousel.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="custom-carousel">
|
||||
<div class="carousel-container">
|
||||
<!-- 轮播内容 -->
|
||||
<div
|
||||
class="carousel-track"
|
||||
:style="{ transform: `translateX(-${currentIndex * 100}%)` }"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
class="carousel-item"
|
||||
>
|
||||
<slot :item="item" :index="index"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左右箭头 -->
|
||||
<button
|
||||
v-if="showArrows"
|
||||
class="carousel-arrow carousel-arrow-left"
|
||||
@click="prev"
|
||||
:disabled="currentIndex === 0 && !loop"
|
||||
>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
</button>
|
||||
<button
|
||||
v-if="showArrows"
|
||||
class="carousel-arrow carousel-arrow-right"
|
||||
@click="next"
|
||||
:disabled="currentIndex === items.length - 1 && !loop"
|
||||
>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 指示器 -->
|
||||
<div
|
||||
v-if="showIndicators"
|
||||
class="carousel-indicators"
|
||||
:class="indicatorPosition"
|
||||
>
|
||||
<button
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
class="indicator-item"
|
||||
:class="{ active: currentIndex === index }"
|
||||
@click="goTo(index)"
|
||||
>
|
||||
<img
|
||||
v-if="currentIndex === index && activeIcon"
|
||||
:src="activeIcon"
|
||||
class="indicator-icon"
|
||||
/>
|
||||
<span v-else class="indicator-dot"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
items: any[];
|
||||
autoplay?: boolean;
|
||||
interval?: number;
|
||||
loop?: boolean;
|
||||
showArrows?: boolean;
|
||||
showIndicators?: boolean;
|
||||
indicatorPosition?: 'bottom-center' | 'bottom-right' | 'bottom-left';
|
||||
activeIcon?: string;
|
||||
inactiveIcon?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
autoplay: true,
|
||||
interval: 5000,
|
||||
loop: true,
|
||||
showArrows: true,
|
||||
showIndicators: true,
|
||||
indicatorPosition: 'bottom-right',
|
||||
});
|
||||
|
||||
const currentIndex = ref(0);
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
function next() {
|
||||
if (currentIndex.value < props.items.length - 1) {
|
||||
currentIndex.value++;
|
||||
} else if (props.loop) {
|
||||
currentIndex.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function prev() {
|
||||
if (currentIndex.value > 0) {
|
||||
currentIndex.value--;
|
||||
} else if (props.loop) {
|
||||
currentIndex.value = props.items.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function goTo(index: number) {
|
||||
currentIndex.value = index;
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
function startAutoplay() {
|
||||
if (props.autoplay) {
|
||||
timer = setInterval(() => {
|
||||
next();
|
||||
}, props.interval);
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoplay() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetTimer() {
|
||||
stopAutoplay();
|
||||
startAutoplay();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startAutoplay();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopAutoplay();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
next,
|
||||
prev,
|
||||
goTo,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-carousel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.carousel-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.carousel-track {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
transition: transform 0.5s ease-in-out;
|
||||
|
||||
.carousel-item {
|
||||
min-width: 100%;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(6.4px);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
z-index: 10;
|
||||
color: #FFFFFF;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.carousel-arrow-left {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&.carousel-arrow-right {
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-indicators {
|
||||
display: flex;
|
||||
gap: 13px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
|
||||
&.bottom-center {
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.bottom-right {
|
||||
bottom: 32px;
|
||||
right: 120px;
|
||||
}
|
||||
|
||||
&.bottom-left {
|
||||
bottom: 32px;
|
||||
left: 120px;
|
||||
}
|
||||
|
||||
.indicator-item {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: all 0.3s;
|
||||
|
||||
.indicator-dot {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: rgba(191, 191, 191, 0.5);
|
||||
backdrop-filter: blur(6.4px);
|
||||
}
|
||||
|
||||
.indicator-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&:hover .indicator-dot {
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&.active .indicator-dot {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as Breadcrumb } from './Breadcrumb.vue';
|
||||
export { default as Carousel } from './Carousel.vue';
|
||||
export { default as FloatingSidebar } from './FloatingSidebar.vue';
|
||||
export { default as MenuItem } from './MenuItem.vue';
|
||||
export { default as MenuNav } from './MenuNav.vue';
|
||||
|
||||
Reference in New Issue
Block a user