281 lines
5.8 KiB
Vue
281 lines
5.8 KiB
Vue
<template>
|
|
<view
|
|
class="cl-banner"
|
|
:class="[`cl-banner--${type}`, `cl-banner--dot-${dotType}`]"
|
|
:style="{ height: parseRpx(height) }"
|
|
@tap="onTap"
|
|
>
|
|
<swiper
|
|
:indicator-dots="indicatorDots"
|
|
:indicator-color="indicatorColor"
|
|
:indicator-active-color="indicatorActiveColor"
|
|
:active-class="activeClass"
|
|
:changing-class="changingClass"
|
|
:autoplay="autoplay"
|
|
:current="current"
|
|
:interval="interval"
|
|
:duration="duration"
|
|
:circular="circular"
|
|
:vertical="vertical"
|
|
:previous-margin="previousMargin"
|
|
:next-margin="nextMargin"
|
|
:acceleration="acceleration"
|
|
:disable-programmatic-animation="disableProgrammaticAnimation"
|
|
:display-multiple-items="displayMultipleItems"
|
|
:skip-hidden-item-layout="skipHiddenItemLayout"
|
|
:disable-touch="disableTouch"
|
|
:easing-function="easingFunction"
|
|
@change="onChange"
|
|
@transition="onTransition"
|
|
@animationfinish="onAnimationfinish"
|
|
>
|
|
<swiper-item v-for="(item, index) in list" :key="index">
|
|
<view
|
|
class="cl-banner-item"
|
|
:class="[
|
|
{
|
|
'is-active': current === index,
|
|
},
|
|
]"
|
|
>
|
|
<slot name="item" :item="item" :index="index">
|
|
<image
|
|
class="cl-banner-item__image"
|
|
:style="{
|
|
'border-radius': parseRpx(radius),
|
|
}"
|
|
:mode="imageMode"
|
|
:src="item.url"
|
|
/>
|
|
</slot>
|
|
</view>
|
|
</swiper-item>
|
|
</swiper>
|
|
|
|
<view class="cl-banner__btn" v-if="arrow">
|
|
<button class="cl-banner__btn-prev" @tap="onPrev" v-if="isPrev">
|
|
<i class="cl-icon-arrow-left"></i>
|
|
</button>
|
|
<button class="cl-banner__btn-next" @tap="onNext" v-if="isNext">
|
|
<i class="cl-icon-arrow-right"></i>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { computed, defineComponent, ref, watch } from "vue";
|
|
import type { PropType } from "vue";
|
|
import { parseRpx } from "/@/cool/utils";
|
|
|
|
export default defineComponent({
|
|
name: "cl-banner",
|
|
|
|
props: {
|
|
// 绑定值
|
|
modelValue: Number,
|
|
// 类型
|
|
type: String as PropType<"chain" | "card" | "">,
|
|
// 是否显示切换箭头
|
|
arrow: Boolean,
|
|
list: {
|
|
type: Array as PropType<{ url: string }[]>,
|
|
default: () => [],
|
|
},
|
|
// 图片裁剪, 缩放模式
|
|
imageMode: {
|
|
type: String,
|
|
default: "aspectFill",
|
|
},
|
|
// 高
|
|
height: {
|
|
type: [String, Number],
|
|
default: 300,
|
|
},
|
|
// 圆角
|
|
radius: {
|
|
type: [String, Number],
|
|
default: 10,
|
|
},
|
|
// 间距
|
|
margin: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
indicatorDots: Boolean,
|
|
indicatorColor: {
|
|
type: String,
|
|
default: "rgba(0, 0, 0, .3)",
|
|
},
|
|
indicatorActiveColor: {
|
|
type: String,
|
|
default: "#000000",
|
|
},
|
|
// 指示器类型
|
|
dotType: {
|
|
type: String as PropType<"round" | "circle" | "square">,
|
|
default: "round",
|
|
},
|
|
activeClass: String,
|
|
changingClass: String,
|
|
autoplay: Boolean,
|
|
currentItemId: String,
|
|
interval: {
|
|
type: Number,
|
|
default: 5000,
|
|
},
|
|
duration: {
|
|
type: Number,
|
|
default: 500,
|
|
},
|
|
circular: Boolean,
|
|
vertical: Boolean,
|
|
acceleration: Boolean,
|
|
disableProgrammaticAnimation: Boolean,
|
|
displayMultipleItems: Number,
|
|
skipHiddenItemLayout: Boolean,
|
|
disableTouch: Boolean,
|
|
touchable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
easingFunction: {
|
|
type: String,
|
|
default: "default",
|
|
},
|
|
},
|
|
|
|
emits: ["update:modelValue", "change", "select", "transition", "animationfinish"],
|
|
|
|
setup(props, { emit }) {
|
|
const current = ref(0);
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(val: any) => {
|
|
current.value = val || 0;
|
|
},
|
|
{
|
|
immediate: true,
|
|
},
|
|
);
|
|
|
|
// 上一个间距
|
|
const previousMargin = computed(() => {
|
|
let [a, b, c] = props.margin;
|
|
|
|
switch (props.type) {
|
|
case "chain":
|
|
switch (current.value) {
|
|
case 0:
|
|
return a || "0";
|
|
case (props.list || []).length - 1:
|
|
return c || "120rpx";
|
|
default:
|
|
return b || "70rpx";
|
|
}
|
|
case "card":
|
|
return a || "80rpx";
|
|
default:
|
|
return a || "0";
|
|
}
|
|
});
|
|
|
|
// 下一个间距
|
|
const nextMargin = computed(() => {
|
|
let [a, b, c] = props.margin;
|
|
|
|
switch (props.type) {
|
|
case "chain":
|
|
switch (current.value) {
|
|
case 0:
|
|
return c || "120rpx";
|
|
case (props.list || []).length - 1:
|
|
return a || "0";
|
|
default:
|
|
return b || "70rpx";
|
|
}
|
|
case "card":
|
|
return b || "80rpx";
|
|
default:
|
|
return b || "0";
|
|
}
|
|
});
|
|
|
|
// 是否采用衔接滑动
|
|
const circular = computed(() => {
|
|
switch (props.type) {
|
|
case "card":
|
|
return true;
|
|
default:
|
|
return props.circular;
|
|
}
|
|
});
|
|
|
|
// 指示点颜色
|
|
const indicatorColor = computed(() => (props.dotType ? "" : props.indicatorColor));
|
|
|
|
// 当前选中的指示点颜色
|
|
const indicatorActiveColor = computed(() =>
|
|
props.dotType ? "" : props.indicatorActiveColor,
|
|
);
|
|
|
|
// 是否有上一个
|
|
const isPrev = computed(() => circular.value || current.value > 0);
|
|
|
|
// 是否有下一个
|
|
const isNext = computed(() => circular.value || current.value < props.list.length - 1);
|
|
|
|
// 监听值变化
|
|
function onChange(e: any) {
|
|
current.value = e.detail.current;
|
|
emit("update:modelValue", current.value);
|
|
emit("change", current.value);
|
|
}
|
|
|
|
// swiper-item 的位置发生改变时会触发
|
|
function onTransition(e: any) {
|
|
emit("transition", e);
|
|
}
|
|
|
|
// 动画结束时会触发
|
|
function onAnimationfinish(e: any) {
|
|
emit("animationfinish", e);
|
|
}
|
|
|
|
// 上一个
|
|
function onPrev() {
|
|
current.value -= isPrev.value ? 1 : 0;
|
|
}
|
|
|
|
// 下一个
|
|
function onNext() {
|
|
current.value += isNext.value ? 1 : 0;
|
|
}
|
|
|
|
// 点击,返回序号
|
|
function onTap() {
|
|
emit("select", current.value);
|
|
}
|
|
|
|
return {
|
|
current,
|
|
isPrev,
|
|
isNext,
|
|
circular,
|
|
previousMargin,
|
|
nextMargin,
|
|
indicatorColor,
|
|
indicatorActiveColor,
|
|
onChange,
|
|
onTransition,
|
|
onAnimationfinish,
|
|
onPrev,
|
|
onNext,
|
|
onTap,
|
|
parseRpx,
|
|
};
|
|
},
|
|
});
|
|
</script>
|