feat: 调整相关商品

This commit is contained in:
duanshuwen
2025-11-07 21:15:28 +08:00
parent 0bbd5a912e
commit 213c58e5c5
2 changed files with 130 additions and 86 deletions

View File

@@ -1,13 +1,13 @@
<template> <template>
<view class="swipe-cards" @touchmove.stop> <view class="swipe-cards border-box" @touchmove.stop>
<view <view
v-for="(card, index) in visibleCards" v-for="(card, index) in visibleCards"
:key="`${card.commodityId}-${index}`" :key="`${card.commodityId}-${index}`"
class="card" class="card"
:style="cardStyle(index, card)" :style="getCardStyle(index, card)"
@touchstart="touchStart($event, index)" @touchstart.stop.capture="handleTouchStart($event, index)"
@touchmove="touchMove($event, index)" @touchmove.stop.capture="handleTouchMove($event, index)"
@touchend="touchEnd($event, index)" @touchend.stop.capture="handleTouchEnd($event, index)"
> >
<slot :card="card" /> <slot :card="card" />
</view> </view>
@@ -15,10 +15,10 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, nextTick } from "vue"; import { ref, computed, nextTick, defineProps } from "vue";
const props = defineProps({ const props = defineProps({
cardsData: Array, cardsData: { type: Array, default: () => [] },
threshold: { type: Number, default: 100 }, threshold: { type: Number, default: 100 },
}); });
@@ -27,133 +27,174 @@ const startX = ref(0);
const startY = ref(0); const startY = ref(0);
const moveX = ref(0); const moveX = ref(0);
const moveY = ref(0); const moveY = ref(0);
// 原始位移用于判定手势,不参与频繁样式更新以减少卡顿
const rawMoveX = ref(0);
const rawMoveY = ref(0);
const currentIndex = ref(null); const currentIndex = ref(null);
const isHorizontal = ref(false); // 是否为水平滑动 const isHorizontal = ref(false);
// 简易节流:避免每次 touchmove 都触发样式更新
let dragTicking = false; // 使用对象存储卡片状态,减少响应式数据
const cardStates = ref({});
// 节流控制
let lastMoveTime = 0;
const THROTTLE_DELAY = 8; // 8ms 节流比16ms更流畅
const visibleCards = computed(() => cards.value.slice(0, 3)); const visibleCards = computed(() => cards.value.slice(0, 3));
function touchStart(e, index) { // 预计算样式常量
const BASE_SCALE = 1;
const SCALE_STEP = 0.05;
const TRANSLATE_Y_STEP = 28;
const ROTATE_RATIO = 24;
function handleTouchStart(e, index) {
const touch = e.touches[0]; const touch = e.touches[0];
startX.value = touch.clientX; startX.value = touch.clientX;
startY.value = touch.clientY; startY.value = touch.clientY;
currentIndex.value = index; currentIndex.value = index;
isHorizontal.value = false; isHorizontal.value = false;
// 初始化卡片状态
if (!cardStates.value[index]) {
cardStates.value[index] = {
swiped: false,
direction: 0,
transform: "",
opacity: 1,
transition: "none",
};
}
} }
function touchMove(e, index) { function handleTouchMove(e, index) {
if (currentIndex.value !== index) return; if (currentIndex.value !== index) return;
const now = Date.now();
if (now - lastMoveTime < THROTTLE_DELAY) return;
lastMoveTime = now;
const touch = e.touches[0]; const touch = e.touches[0];
rawMoveX.value = touch.clientX - startX.value; const deltaX = touch.clientX - startX.value;
rawMoveY.value = touch.clientY - startY.value; const deltaY = touch.clientY - startY.value;
// 第一次移动判断方向 // 次移动判断方向
if ( if (!isHorizontal.value) {
!isHorizontal.value && const absX = Math.abs(deltaX);
(Math.abs(rawMoveX.value) > 10 || Math.abs(rawMoveY.value) > 10) const absY = Math.abs(deltaY);
) { if (absX > 10 || absY > 10) {
if (Math.abs(rawMoveX.value) > Math.abs(rawMoveY.value)) { isHorizontal.value = absX > absY;
isHorizontal.value = true;
} }
} }
// 如果是水平滑动,阻止页面滚动
if (isHorizontal.value) { if (isHorizontal.value) {
e.preventDefault?.(); e.preventDefault?.();
// 将样式更新按 16ms 节流,降低渲染压力 moveX.value = deltaX;
if (!dragTicking) { moveY.value = deltaY;
dragTicking = true;
setTimeout(() => { // 更新当前卡片的transform避免重复计算
moveX.value = rawMoveX.value; updateCardTransform(index, deltaX, deltaY);
moveY.value = rawMoveY.value;
dragTicking = false;
}, 16);
}
} }
} }
function touchEnd(e, index) { function handleTouchEnd(e, index) {
if (!isHorizontal.value) return; // 如果不是水平滑动,不处理 if (!isHorizontal.value) return;
const deltaX = Math.abs(rawMoveX.value);
const deltaY = Math.abs(rawMoveY.value); const absX = Math.abs(moveX.value);
if (deltaX > props.threshold && deltaX >= deltaY) { const absY = Math.abs(moveY.value);
const direction = rawMoveX.value > 0 ? 1 : -1;
if (absX > props.threshold && absX >= absY) {
const direction = moveX.value > 0 ? 1 : -1;
animateSwipe(index, direction); animateSwipe(index, direction);
} else { } else {
resetCard(index); resetCard(index);
} }
// 重置状态
moveX.value = 0; moveX.value = 0;
moveY.value = 0; moveY.value = 0;
rawMoveX.value = 0;
rawMoveY.value = 0;
currentIndex.value = null; currentIndex.value = null;
isHorizontal.value = false;
}
function updateCardTransform(index, deltaX, deltaY) {
const state = cardStates.value[index];
if (!state) return;
const rotate = deltaX / ROTATE_RATIO;
state.transform = `translate(${deltaX}px, ${deltaY}px) rotate(${rotate}deg)`;
state.transition = "none";
} }
function animateSwipe(index, direction) { function animateSwipe(index, direction) {
const card = cards.value[index]; const state = cardStates.value[index];
if (!card) return; if (!state) return;
card.swiped = true;
card.direction = direction; state.swiped = true;
state.direction = direction;
state.transform = `translate(${direction * 500}px, ${moveY.value}px) rotate(${
direction * 45
}deg)`;
state.opacity = 0;
state.transition = "all 0.3s ease-out";
setTimeout(async () => { setTimeout(async () => {
const removed = cards.value.splice(index, 1)[0]; const removed = cards.value.splice(index, 1)[0];
removed.swiped = false; removed.swiped = false;
removed.direction = 0; removed.direction = 0;
cards.value.push(removed); cards.value.push(removed);
// 清理状态
delete cardStates.value[index];
await nextTick(); await nextTick();
}, 300); }, 300);
} }
function resetCard(index) { function resetCard(index) {
const card = cards.value[index]; const state = cardStates.value[index];
if (!card) return; if (!state) return;
card.swiped = false;
card.direction = 0; state.swiped = false;
state.direction = 0;
state.transform = "";
state.transition = "transform 0.2s ease-out";
} }
function cardStyle(index, card) { function getCardStyle(index, card) {
const baseIndex = visibleCards.value.length - index; // 如果有触摸状态,使用预计算的样式
const zIndex = baseIndex; if (cardStates.value[index] && currentIndex.value === index) {
const scale = 1 - index * 0.05; const state = cardStates.value[index];
const translateY = index * 28; return {
zIndex: 3 - index,
transform: state.transform || "translate(0, 0)",
opacity: state.opacity,
transition: state.transition,
willChange: "transform, opacity",
backfaceVisibility: "hidden",
transformStyle: "preserve-3d",
};
}
const translateDrag = // 静态卡片使用简化计算
currentIndex.value === index const zIndex = 3 - index;
? `translate(${moveX.value}px, ${moveY.value}px)` const scale = BASE_SCALE - index * SCALE_STEP;
: `translate(0, ${translateY}rpx)`; const translateY = index * TRANSLATE_Y_STEP;
const rotate = const opacity = card.swiped ? 0 : 1;
currentIndex.value === index
? `rotate(${moveX.value / 24}deg)`
: "rotate(0)";
let opacity = 1; let transform = `translate(0, ${translateY}rpx) scale(${scale})`;
if (card.swiped) opacity = 0;
let transform = `${translateDrag} scale(${scale}) ${rotate}`;
if (card.swiped) { if (card.swiped) {
const x = card.direction * 500; transform = `translate(${card.direction * 500}px, 0) rotate(${
transform = `translate(${x}px, ${moveY.value}px) rotate(${
card.direction * 45 card.direction * 45
}deg)`; }deg)`;
} }
return ` return {
z-index: ${zIndex}; zIndex,
background: ${card.color}; transform,
transform: ${transform}; opacity,
opacity: ${opacity}; transition: card.swiped ? "all 0.3s ease-out" : "transform 0.2s ease-out",
transition: ${ willChange: "transform, opacity",
card.swiped backfaceVisibility: "hidden",
? "all 0.3s ease-out" transformStyle: "preserve-3d",
: currentIndex.value === index && isHorizontal.value
? "none"
: "transform 0.2s ease-out"
}; };
`;
} }
</script> </script>
@@ -163,15 +204,18 @@ function cardStyle(index, card) {
height: 320px; height: 320px;
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 6px 6px 12px 6px; padding: 6px 0 12px;
will-change: transform; // 启用硬件加速
} }
.card { .card {
position: absolute; position: absolute;
width: calc(100% - 12px); width: 100%;
height: calc(100% - 30px); height: calc(100% - 30px);
border-radius: 20px; border-radius: 20px;
box-shadow: 0 8px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 8px 8px rgba(0, 0, 0, 0.08);
background: #fff; background: #fff;
will-change: transform, opacity; // 启用硬件加速
backface-visibility: hidden; // 减少重绘
} }
</style> </style>

View File

@@ -6,7 +6,7 @@
<template #default="{ card }"> <template #default="{ card }">
<view <view
class="inner-card overflow-hidden" class="inner-card overflow-hidden"
@click="placeOrderHandle(card)" @click.stop.prevent.capture="placeOrderHandle(card)"
> >
<!-- 商品大图部分自适应剩余空间 --> <!-- 商品大图部分自适应剩余空间 -->
<view class="goods-image-wrapper"> <view class="goods-image-wrapper">