feat: 调整相关商品
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user