Compare commits

2 Commits

Author SHA1 Message Date
c989d5b4ab feat: 请求首页数据不滚到底部 2025-10-31 01:17:36 +08:00
98e6c55ccf feat: 实现相关商品的样式调整 2025-10-31 01:16:00 +08:00
4 changed files with 313 additions and 173 deletions

View File

@@ -0,0 +1,155 @@
<template>
<view class="swipe-cards" @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)"
>
<slot :card="card" />
</view>
</view>
</template>
<script setup>
import { ref, computed, nextTick } from "vue";
const props = defineProps({
cardsData: Array,
threshold: { type: Number, default: 100 },
});
const cards = ref([...props.cardsData]);
const startX = ref(0);
const startY = ref(0);
const moveX = ref(0);
const moveY = ref(0);
const currentIndex = ref(null);
const isHorizontal = ref(false); // 是否为水平滑动
const visibleCards = computed(() => cards.value.slice(0, 3));
function touchStart(e, index) {
const touch = e.touches[0];
startX.value = touch.clientX;
startY.value = touch.clientY;
currentIndex.value = index;
isHorizontal.value = false;
}
function touchMove(e, index) {
if (currentIndex.value !== index) return;
const touch = e.touches[0];
moveX.value = touch.clientX - startX.value;
moveY.value = touch.clientY - startY.value;
// 第一次移动时判断方向
if (
!isHorizontal.value &&
(Math.abs(moveX.value) > 10 || Math.abs(moveY.value) > 10)
) {
if (Math.abs(moveX.value) > Math.abs(moveY.value)) {
isHorizontal.value = true;
}
}
// 如果是水平滑动,阻止页面滚动
if (isHorizontal.value) {
e.preventDefault?.();
}
}
function touchEnd(e, index) {
if (!isHorizontal.value) return; // 如果不是水平滑动,不处理
if (Math.abs(moveX.value) > props.threshold) {
const direction = moveX.value > 0 ? 1 : -1;
animateSwipe(index, direction);
} else {
resetCard(index);
}
moveX.value = 0;
moveY.value = 0;
currentIndex.value = null;
}
function animateSwipe(index, direction) {
const card = cards.value[index];
if (!card) return;
card.swiped = true;
card.direction = direction;
setTimeout(async () => {
const removed = cards.value.splice(index, 1)[0];
removed.swiped = false;
removed.direction = 0;
cards.value.push(removed);
await nextTick();
}, 300);
}
function resetCard(index) {
const card = cards.value[index];
if (!card) return;
card.swiped = false;
card.direction = 0;
}
function cardStyle(index, card) {
const baseIndex = visibleCards.value.length - index;
const zIndex = baseIndex;
const scale = 1 - index * 0.05;
const translateY = index * 28;
const translateDrag =
currentIndex.value === index
? `translate(${moveX.value}px, ${moveY.value}px)`
: `translate(0, ${translateY}rpx)`;
const rotate =
currentIndex.value === index
? `rotate(${moveX.value / 15}deg)`
: "rotate(0)";
let opacity = 1;
if (card.swiped) opacity = 0;
let transform = `${translateDrag} scale(${scale}) ${rotate}`;
if (card.swiped) {
const x = card.direction * 500;
transform = `translate(${x}px, ${moveY.value}px) rotate(${
card.direction * 45
}deg)`;
}
return `
z-index: ${zIndex};
background: ${card.color};
transform: ${transform};
opacity: ${opacity};
transition: ${
card.swiped ? "all 0.3s ease-out" : "transform 0.2s ease-out"
};
`;
}
</script>
<style scoped lang="scss">
.swipe-cards {
position: relative;
height: 320px;
display: flex;
justify-content: center;
padding: 6px 6px 12px 6px;
}
.card {
position: absolute;
width: calc(100% - 12px);
height: calc(100% - 30px);
border-radius: 20px;
box-shadow: 0 8px 8px rgba(0, 0, 0, 0.08);
background: #fff;
}
</style>

View File

@@ -394,7 +394,6 @@ const getMainPageData = async () => {
initData(); initData();
mainPageDataModel.value = res.data; mainPageDataModel.value = res.data;
agentId.value = res.data.agentId; agentId.value = res.data.agentId;
setTimeoutScrollToBottom();
} }
appStore.setSceneId(""); // 清空sceneId,分身二次进入展示默认的 appStore.setSceneId(""); // 清空sceneId,分身二次进入展示默认的
}; };

View File

@@ -1,48 +1,49 @@
<template> <template>
<view class="container"> <view class="container">
<ModuleTitle title="相关商品" /> <ModuleTitle title="相关商品" />
<view class="container-scroll">
<view <SwipeCards :cardsData="commodityList">
v-for="(item, index) in commodityList" <template #default="{ card }">
:key="`${item.commodityId}-${index}`" <view class="inner-card" @click="placeOrderHandle(card)">
> <!-- 商品大图部分自适应剩余空间 -->
<view class="mk-card-item" @click="placeOrderHandle(item)"> <view class="goods-image-wrapper">
<image <image
class="card-img" class="goods-image"
:src="item.commodityPhoto" :src="card.commodityPhoto"
mode="aspectFill" mode="aspectFill"
/> ></image>
<view class="card-content"> <view class="goods-title">
<view class="card-title-column"> <text class="goods-text">{{ card.commodityName }}</text>
<text class="card-title">{{ item.commodityName }}</text>
<view
class="card-tags"
v-for="tag in item.commodityTradeRuleList"
:key="tag"
>
<text class="card-tag">{{ tag }}</text>
</view>
</view>
<template
v-for="(serviceItem, index) in item.commodityServices"
:key="serviceItem.serviceTitle"
>
<view v-if="index < 3" class="card-desc"
>· {{ serviceItem.serviceTitle }}</view
>
</template>
<view class="card-bottom-row">
<view class="card-price-row"> <view class="card-price-row">
<text class="card-price-fu"></text> <text class="card-price-fu"></text>
<text class="card-price">{{ item.specificationPrice }}</text> <text class="card-price">{{ card.specificationPrice }}</text>
<text class="card-unit">/{{ item.stockUnitLabel }} </text> <text class="card-unit" v-if="card.stockUnitLabel"
>/{{ card.stockUnitLabel }}</text
>
</view>
</view>
</view>
<!-- 底部相册部分固定比例或高度 -->
<view class="album-row">
<view
v-for="(item, index) in normalizedAlbums(commodityList)"
:key="index"
class="album-item"
>
<view v-if="item" class="album-image-wrapper">
<image
class="album-image"
:src="item.commodityPhoto"
mode="aspectFill"
/>
<view class="album-title">{{ item.commodityName }}</view>
</view> </view>
<text class="card-btn">下单</text>
</view> </view>
</view> </view>
</view> </view>
</view> </template>
</view> </SwipeCards>
</view> </view>
</template> </template>
@@ -50,6 +51,7 @@
import { defineProps } from "vue"; import { defineProps } from "vue";
import { checkToken } from "@/hooks/useGoLogin"; import { checkToken } from "@/hooks/useGoLogin";
import ModuleTitle from "@/components/ModuleTitle/index.vue"; import ModuleTitle from "@/components/ModuleTitle/index.vue";
import SwipeCards from "@/components/SwipeCards/index.vue";
const props = defineProps({ const props = defineProps({
commodityList: { commodityList: {
@@ -58,7 +60,14 @@ const props = defineProps({
}, },
}); });
/// 去下单 // 补齐3个的
const normalizedAlbums = (list = []) => {
const arr = list.slice(0, 3);
while (arr.length < 3) arr.push(null);
return arr;
};
// 去下单
const placeOrderHandle = (item) => { const placeOrderHandle = (item) => {
checkToken().then(() => { checkToken().then(() => {
uni.navigateTo({ uni.navigateTo({
@@ -70,4 +79,116 @@ const placeOrderHandle = (item) => {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./styles/index.scss"; @import "./styles/index.scss";
.inner-card {
width: 100%;
height: 100%;
background: #fff;
border-radius: 20px;
flex-direction: column;
// overflow: hidden;
}
/* 商品大图部分:撑满除相册外的空间 */
.goods-image-wrapper {
position: relative;
width: 100%;
aspect-ratio: 335 / 200;
overflow: hidden;
}
.goods-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.goods-title {
position: absolute;
inset: 0;
padding: 12px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.6) 100%
);
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.goods-text {
color: #fff;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-price-fu {
color: #fff;
font-size: 11px;
font-weight: normal;
}
.card-price {
color: #fff;
font-size: $uni-font-size-lg;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #fff;
font-weight: normal;
margin-left: 2px;
}
.album-row {
display: flex;
justify-content: space-between;
padding: 12px;
box-sizing: border-box;
flex-shrink: 0;
aspect-ratio: 335 / 84;
}
.album-item {
width: calc((100% - 24px) / 3);
height: 100%;
border-radius: 10px;
overflow: hidden;
background: #f5f5f5;
}
.album-image-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.album-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.album-title {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 8px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.6) 100%
);
color: #fff;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>

View File

@@ -1,138 +1,3 @@
.container { .container {
margin: 6px 0 0; margin: 6px 0 0;
.container-scroll {
display: flex;
flex-direction: row;
overflow-x: auto;
overflow-y: hidden;
margin: 4px 0;
.mk-card-item {
position: relative;
display: flex;
flex-direction: column;
align-items: start;
width: 188px;
background-color: $uni-bg-color;
border-radius: 10px;
margin-right: 8px;
padding-bottom: 12px;
.card-badge {
position: absolute;
top: 8px;
left: 8px;
background: #ffe7b2;
color: #b97a00;
font-size: $uni-font-size-sm;
padding: 2px 8px;
border-radius: 4px;
z-index: 2;
}
.card-img {
width: 188px;
height: 114px;
border-radius: 10px;
object-fit: cover; /* 确保图片不变形,保持比例裁剪 */
flex-shrink: 0; /* 防止图片被压缩 */
}
.card-content {
box-sizing: border-box;
padding: 10px 12px 0 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: start;
width: 100%;
flex: 1; /* 让内容区域占据剩余空间 */
overflow: hidden; /* 防止内容溢出 */
}
.card-title-column {
display: flex;
align-items: start;
flex-direction: column;
width: 100%;
}
.card-title {
font-size: $uni-font-size-lg;
font-weight: bold;
color: #222;
width: 100%;
/* 限制标题最多显示两行 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.4;
max-height: 2.8em; /* 2行的高度 */
}
.card-tags {
display: flex;
flex-direction: row;
align-items: start;
padding: 6px 0;
}
.card-tag {
color: #ff6600;
font-size: 10px;
border-radius: 4px;
padding: 0 6px;
margin-left: 2px;
border: 1px solid #ff6600;
}
.card-desc {
font-size: 13px;
color: #888;
margin-top: 2px;
}
.card-bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
width: 100%;
}
.card-price-row {
.card-price-fu {
color: #ff6600;
font-size: 11px;
font-weight: normal;
}
.card-price {
color: #ff6600;
font-size: $uni-font-size-lg;
font-weight: bold;
}
.card-unit {
font-size: 11px;
color: #888;
font-weight: normal;
margin-left: 2px;
}
}
.card-btn {
background: #ff6600;
color: #fff;
font-size: 15px;
border-radius: 20px;
padding: 0 18px;
height: 32px;
line-height: 32px;
}
}
}
} }