feat: 轮播改造为 只要有一条数据就能轮播

This commit is contained in:
2026-04-29 17:06:56 +08:00
parent 4625b3c840
commit 7d1deb3a80

View File

@@ -1,7 +1,7 @@
<template> <template>
<view <view
class="card-swiper" class="card-swiper"
:class="{ 'is-single': !canLoop }" :class="{ 'is-single': props.list.length <= 1 }"
@touchstart="handleTouchStart" @touchstart="handleTouchStart"
@touchmove.stop.prevent="handleTouchMove" @touchmove.stop.prevent="handleTouchMove"
@touchend="handleTouchEnd" @touchend="handleTouchEnd"
@@ -34,7 +34,7 @@
</view> </view>
<view <view
v-if="canLoop" v-if="canSwipe"
class="card-mask" class="card-mask"
:style="getMaskStyle(slot.role)" :style="getMaskStyle(slot.role)"
/> />
@@ -71,7 +71,7 @@ const { windowWidth = 375 } = uni.getWindowInfo();
const sideOffset = Math.max(108, Math.min(windowWidth * 0.26, 148)); const sideOffset = Math.max(108, Math.min(windowWidth * 0.26, 148));
const hiddenOffset = sideOffset + 92; const hiddenOffset = sideOffset + 92;
const activeIndex = ref(0); const activeCursor = ref(0);
const deltaX = ref(0); const deltaX = ref(0);
const isDragging = ref(false); const isDragging = ref(false);
const isAnimating = ref(false); const isAnimating = ref(false);
@@ -87,9 +87,14 @@ const hasExternalModel = Object.prototype.hasOwnProperty.call(
"modelValue" "modelValue"
); );
const canLoop = computed(() => props.list.length > 1); const actualCount = computed(() => props.list.length);
const virtualCount = computed(() => {
if (actualCount.value === 1) return 3;
return actualCount.value;
});
const canSwipe = computed(() => virtualCount.value > 1);
const progress = computed(() => { const progress = computed(() => {
if (!canLoop.value) return 0; if (!canSwipe.value) return 0;
return clamp(deltaX.value / sideOffset, -1, 1); return clamp(deltaX.value / sideOffset, -1, 1);
}); });
@@ -98,8 +103,18 @@ const normalizeIndex = (index, total) => {
return ((index % total) + total) % total; return ((index % total) + total) % total;
}; };
const syncActiveIndex = (incomingIndex = 0) => { const getActualIndex = (virtualIndex) => {
activeIndex.value = normalizeIndex(incomingIndex, props.list.length); if (!actualCount.value) return 0;
return normalizeIndex(virtualIndex, actualCount.value);
};
const getItemByVirtualIndex = (virtualIndex) => {
if (!actualCount.value) return null;
return props.list[getActualIndex(virtualIndex)] || null;
};
const syncActiveCursor = (incomingIndex = 0) => {
activeCursor.value = normalizeIndex(incomingIndex, virtualCount.value);
}; };
function clearSettleTimer() { function clearSettleTimer() {
@@ -116,10 +131,10 @@ watch(
deltaX.value = 0; deltaX.value = 0;
isDragging.value = false; isDragging.value = false;
isAnimating.value = false; isAnimating.value = false;
const nextIndex = hasExternalModel const nextCursor = hasExternalModel
? props.modelValue ? props.modelValue
: activeIndex.value; : activeCursor.value;
syncActiveIndex(nextIndex); syncActiveCursor(nextCursor);
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
@@ -129,23 +144,20 @@ watch(
(value) => { (value) => {
if (!hasExternalModel) return; if (!hasExternalModel) return;
if (isAnimating.value || isDragging.value) return; if (isAnimating.value || isDragging.value) return;
syncActiveIndex(value); syncActiveCursor(value);
} }
); );
const getItemKey = (index, role) => { const getItemKey = (virtualIndex, role) => {
const item = props.list[index] || {}; const item = getItemByVirtualIndex(virtualIndex) || {};
const baseKey = const baseKey =
item.id ?? item.id ??
item.commodityId ?? item.commodityId ??
item.tabLabel ?? item.tabLabel ??
item.title ?? item.title ??
index; virtualIndex;
const prevIndex = normalizeIndex(activeIndex.value - 1, props.list.length);
const nextIndex = normalizeIndex(activeIndex.value + 1, props.list.length);
const hasDuplicateSide = prevIndex === nextIndex && role !== "current";
return hasDuplicateSide ? `${baseKey}-${role}` : `${baseKey}`; return `${baseKey}-${virtualIndex}-${role}`;
}; };
const states = { const states = {
@@ -216,7 +228,7 @@ const getCardZIndex = (role) => {
}; };
const getSlotState = (role) => { const getSlotState = (role) => {
if (!canLoop.value) return states.center; if (!canSwipe.value) return states.center;
const currentProgress = progress.value; const currentProgress = progress.value;
if (currentProgress < 0) { if (currentProgress < 0) {
@@ -251,7 +263,7 @@ const buildCardStyle = (role) => {
}; };
const getMaskOpacity = (role) => { const getMaskOpacity = (role) => {
if (!canLoop.value) return 0; if (!canSwipe.value) return 0;
const currentProgress = progress.value; const currentProgress = progress.value;
const baseOpacity = 0.42; const baseOpacity = 0.42;
@@ -279,43 +291,31 @@ const getMaskStyle = (role) => ({
}); });
const renderSlots = computed(() => { const renderSlots = computed(() => {
if (!props.list.length) return []; if (!actualCount.value) return [];
if (!canLoop.value) { const prevIndex = normalizeIndex(activeCursor.value - 1, virtualCount.value);
return [ const nextIndex = normalizeIndex(activeCursor.value + 1, virtualCount.value);
{
key: getItemKey(activeIndex.value, "current"),
role: "current",
index: activeIndex.value,
item: props.list[activeIndex.value],
style: buildCardStyle("current"),
},
];
}
const prevIndex = normalizeIndex(activeIndex.value - 1, props.list.length);
const nextIndex = normalizeIndex(activeIndex.value + 1, props.list.length);
return [ return [
{ {
key: getItemKey(prevIndex, "prev"), key: getItemKey(prevIndex, "prev"),
role: "prev", role: "prev",
index: prevIndex, index: getActualIndex(prevIndex),
item: props.list[prevIndex], item: getItemByVirtualIndex(prevIndex),
style: buildCardStyle("prev"), style: buildCardStyle("prev"),
}, },
{ {
key: getItemKey(activeIndex.value, "current"), key: getItemKey(activeCursor.value, "current"),
role: "current", role: "current",
index: activeIndex.value, index: getActualIndex(activeCursor.value),
item: props.list[activeIndex.value], item: getItemByVirtualIndex(activeCursor.value),
style: buildCardStyle("current"), style: buildCardStyle("current"),
}, },
{ {
key: getItemKey(nextIndex, "next"), key: getItemKey(nextIndex, "next"),
role: "next", role: "next",
index: nextIndex, index: getActualIndex(nextIndex),
item: props.list[nextIndex], item: getItemByVirtualIndex(nextIndex),
style: buildCardStyle("next"), style: buildCardStyle("next"),
}, },
]; ];
@@ -335,16 +335,17 @@ const finishSwipe = (step) => {
deltaX.value = step > 0 ? -sideOffset : sideOffset; deltaX.value = step > 0 ? -sideOffset : sideOffset;
clearSettleTimer(); clearSettleTimer();
settleTimer = setTimeout(() => { settleTimer = setTimeout(() => {
const nextIndex = normalizeIndex(activeIndex.value + step, props.list.length); const nextCursor = normalizeIndex(activeCursor.value + step, virtualCount.value);
activeIndex.value = nextIndex; activeCursor.value = nextCursor;
emit("update:modelValue", nextIndex); const actualIndex = getActualIndex(nextCursor);
emit("change", nextIndex); emit("update:modelValue", actualIndex);
emit("change", actualIndex);
resetGesture(); resetGesture();
}, DURATION); }, DURATION);
}; };
const handleTouchStart = (event) => { const handleTouchStart = (event) => {
if (!canLoop.value || isAnimating.value) return; if (!canSwipe.value || isAnimating.value) return;
const touch = event.touches?.[0] || event.changedTouches?.[0]; const touch = event.touches?.[0] || event.changedTouches?.[0];
if (!touch) return; if (!touch) return;
@@ -357,7 +358,7 @@ const handleTouchStart = (event) => {
}; };
const handleTouchMove = (event) => { const handleTouchMove = (event) => {
if (!canLoop.value || !isDragging.value || isAnimating.value) return; if (!canSwipe.value || !isDragging.value || isAnimating.value) return;
const touch = event.touches?.[0] || event.changedTouches?.[0]; const touch = event.touches?.[0] || event.changedTouches?.[0];
if (!touch) return; if (!touch) return;
@@ -378,7 +379,7 @@ const handleTouchMove = (event) => {
}; };
const handleTouchEnd = () => { const handleTouchEnd = () => {
if (!canLoop.value || !isDragging.value) return; if (!canSwipe.value || !isDragging.value) return;
isDragging.value = false; isDragging.value = false;
@@ -396,7 +397,7 @@ const handleTouchEnd = () => {
}; };
const handleTouchCancel = () => { const handleTouchCancel = () => {
if (!canLoop.value) return; if (!canSwipe.value) return;
clearSettleTimer(); clearSettleTimer();
deltaX.value = 0; deltaX.value = 0;
isDragging.value = false; isDragging.value = false;