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

View File

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