yfs/components/detail-preview-popup/detail-preview-popup.vue
2025-04-22 13:58:23 +08:00

602 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<uni-popup ref="popup" type="center" maskBackgroundColor="rgba(0,0,0,0.9)">
<view v-if="visible" class="preview-popup" @touchmove.stop.prevent="moveHandle">
<!-- 翻转卡片容器 -->
<view class="flip-container" :class="{ 'flipped': showList }">
<view class="flipper">
<!-- 正面:商品图片 -->
<view class="front">
<view class="pic center relative" @click="toggleList">
<image :src="innerImgUrl" lazy-load mode="aspectFill"></image>
<view class="type-tag justify-center"
:style="{ backgroundColor: dataItem.shang_info ? dataItem.shang_info.color : '#000000' }"
v-if="innerTipTitle">{{ innerTipTitle }}</view>
<view class="baoxiang-tag justify-center" v-if="isTips"></view>
</view>
</view>
<!-- 背面:赏品列表 -->
<view class="back">
<view class="listt">
<scroll-view class="list" :scroll-y="true">
<!-- 加载中状态 -->
<view class="loading-container center" v-if="loading">
<view class="loading-icon">
<image :src="$img('/static/img/loading.gif')" mode="aspectFit" @error="handleLoadingError"></image>
<view class="loading-dot" v-if="loadingError"></view>
</view>
<view class="loading-text">加载中...</view>
</view>
<!-- 空列表状态 -->
<view class="empty-container center" v-else-if="children.length === 0 && !loading">
<view class="empty-text">暂无商品</view>
</view>
<!-- 赏品列表内容 -->
<view class="res-list" v-else>
<detail-list-item v-for="(item, i) in children" :key="i" :item="item" imageHeight="180rpx"
@click="handleItemClick" />
</view>
</scroll-view>
</view>
</view>
</view>
</view>
<!-- 商品标题 -->
<view class="title" @click="toggleList">
<text class="hang1">{{ !showList ? innerTitle : '抽中宝箱后,会在奖品列表中随机抽取一个奖品' }}</text>
</view>
<!-- 商品信息列表 -->
<view class="info-list">
<view class="info-item" v-if="innerProbability">{{ innerProbability }}</view>
<view class="info-item highlight" v-if="isTips" @click="toggleList">
{{ showList ? '收起' : '查看奖品' }}
<!-- <text class="arrow-icon">{{ showList ? '↑' : '↓' }}</text> -->
</view>
<view class="info-item" v-if="innerProductType">产品类型: {{ innerProductType }}</view>
<view class="info-item" v-for="(item, index) in innerExtraInfo" :key="'info-' + index">{{ item }}</view>
</view>
<!-- 关闭按钮 -->
<view class="close icon" @click="closePopup">
<image :src="$img('/static/img/close.png')" lazy-load></image>
</view>
</view>
</uni-popup>
</template>
<script>
import DetailListItem from '@/components/detail-list-item/detail-list-item.vue';
export default {
name: 'DetailPreviewPopup',
components: {
DetailListItem
},
props: {
// 商品标题
title: {
type: String,
default: ''
},
// 商品图片地址
imgUrl: {
type: String,
default: ''
},
// 商品提示标签标题
tipTitle: {
type: String,
default: ''
},
// 商品类型
productType: {
type: String,
default: ''
},
// 商品概率
probability: {
type: String,
default: ''
},
// 额外信息(可选)
extraInfo: {
type: Array,
default: () => []
}
},
data() {
return {
visible: false,
// 内部数据,用于显示
innerTitle: '',
innerImgUrl: '',
innerTipTitle: '',
innerProductType: '',
innerProbability: '',
innerExtraInfo: [],
dataItem: {},
showList: false,
isTips: false,
children: [],
goods: null,
loading: false,
loadingError: false,
loadError: false
}
},
watch: {
// 监听props变化更新内部数据
title(val) {
this.innerTitle = val;
},
imgUrl(val) {
this.innerImgUrl = val;
},
tipTitle(val) {
this.innerTipTitle = val;
},
productType(val) {
this.innerProductType = val;
},
probability(val) {
this.innerProbability = val;
},
extraInfo(val) {
this.innerExtraInfo = val;
}
},
created() {
// 初始化内部数据
this.innerTitle = this.title;
this.innerImgUrl = this.imgUrl;
this.innerTipTitle = this.tipTitle;
this.innerProductType = this.productType;
this.innerProbability = this.probability;
this.innerExtraInfo = this.extraInfo;
// 监听全局预览事件
uni.$on('global-preview-event', this.handleGlobalEvent);
},
beforeDestroy() {
// 组件销毁前移除事件监听
uni.$off('global-preview-event', this.handleGlobalEvent);
},
methods: {
moveHandle(e) {
return true;
},
// 处理loading图片加载失败
handleLoadingError() {
this.loadingError = true;
},
// 处理列表项点击
handleItemClick(item) {
console.log('点击了', item);
this.$emit('itemClick', item);
},
// 处理全局预览事件
handleGlobalEvent(options) {
console.log('收到全局预览事件', options);
this.setPreviewData(options);
},
// 切换列表显示状态,添加动画效果
toggleList() {
if (this.isTips) {
if (this.children.length === 0 && !this.loading && !this.loadError) {
this.loadChildren();
}
this.showList = !this.showList;
}
},
// 设置预览数据并打开弹窗
setPreviewData(data) {
if (!data) return;
// 重置数据状态
this.loadError = false;
this.showList = false;
this.isTips = false;
this.dataItem = data.dataItem;
// 更新商品基本信息
if (data.dataItem) {
console.log(data.dataItem.shang_info.color);
const item = data.dataItem;
this.innerTitle = item.title || '';
this.innerImgUrl = item.imgurl || '';
this.innerTipTitle = item.shang_info ? item.shang_info.title : '';
this.innerProbability = item.pro || '';
}
// 更新其他信息
if (data.productType) this.innerProductType = data.productType;
if (data.extraInfo) this.innerExtraInfo = data.extraInfo;
console.log('data.goods', data.goods, data.dataItem);
// 设置商品和数据项
if (data.dataItem && data.goods && data.dataItem.goods_type == 4) {
this.goods = data.goods;
this.isTips = true;
this.children = [];
}
// 打开弹窗
this.open();
},
// 打开弹窗
open() {
this.visible = true;
this.$nextTick(() => {
if (this.$refs.popup) {
// 小程序环境下需要额外延迟
setTimeout(() => {
this.$refs.popup.open();
}, 50);
} else {
console.error('popup ref不存在请检查组件是否正确挂载');
// 延迟后再次尝试
setTimeout(() => {
if (this.$refs.popup) {
this.$refs.popup.open();
}
}, 300);
}
});
},
// 关闭弹窗
closePopup() {
this.visible = false;
if (this.$refs.popup) {
this.$refs.popup.close();
}
this.$emit('close');
},
// 加载子商品数据
async loadChildren() {
if (this.loading) return;
this.loading = true;
this.loadError = false;
try {
const res = await this.req({
url: "goods_children",
method: "POST",
Loading: true, // 已经有自己的loading状态不需要全局loading
data: {
goods_id: this.goods.id,
goods_num: this.goods.num,
goods_list_id: this.dataItem.id
}
});
if (res && res.status === 1) {
this.children = res.data || [];
} else {
this.loadError = true;
this.children = [];
}
} catch (error) {
console.error('加载数据出错', error);
this.loadError = true;
this.children = [];
} finally {
this.loading = false;
}
},
}
}
</script>
<style lang="scss" scoped>
.preview-popup {
position: relative;
width: 80vw;
box-sizing: border-box;
height: 850rpx;
// 翻转卡片相关样式
.flip-container {
perspective: 1000px;
width: 100%;
height: 620rpx;
&.flipped .flipper {
transform: rotateY(180deg);
}
.flipper {
transition: 0.6s;
transform-style: preserve-3d;
position: relative;
width: 100%;
height: 100%;
}
.front,
.back {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.front {
z-index: 2;
transform: rotateY(0deg);
}
.back {
transform: rotateY(180deg);
}
}
.listt {
width: 100%;
padding-top: 5rpx;
height: 620rpx;
background-color: #ffffff;
border-radius: 16rpx;
margin: 0rpx auto;
}
.list {
width: 100%;
height: 610rpx;
background-color: #ffffff;
border-radius: 16rpx;
margin: 0rpx auto;
.res-list {
padding: 5rpx 15rpx;
display: grid;
grid-template-columns: repeat(3, 33%);
gap: 20rpx 8rpx;
justify-content: center;
}
}
// 商品图片区域
.pic {
width: 100%;
height: 620rpx;
background: #FFFFFF;
border-radius: 16rpx;
image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
// 商品标签
.type-tag {
width: 100rpx;
height: 40rpx;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 12rpx;
left: 24rpx;
font-weight: 400;
line-height: 40rpx;
font-size: 20rpx;
color: #FFFFFF;
box-sizing: border-box;
border-radius: 25rpx;
}
.baoxiang-tag {
height: 64rpx;
width: 136rpx;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 12rpx;
right: 24rpx;
font-weight: 400;
line-height: 40rpx;
font-size: 20rpx;
color: #FFFFFF;
background-image: url($iconurl + "baoxiang.png");
background-size: 100% 100%;
box-sizing: border-box;
border-radius: 25rpx;
}
}
// 商品标题
.title {
margin: 40rpx auto 0;
width: 100%;
height: 60rpx;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx;
border-radius: 12rpx;
background: #F5F5F5;
text {
font-weight: 600;
font-size: 24rpx;
color: #333333;
text-align: center;
line-height: 1.4;
}
}
// 商品信息列表
.info-list {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
padding: 20rpx 0 0;
.info-item {
margin-top: 14rpx;
font-size: 24rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #ffffff;
cursor: pointer;
&.highlight {
color: #f39205;
font-weight: 500;
display: flex;
align-items: center;
.arrow-icon {
margin-left: 4rpx;
}
}
}
}
// 关闭按钮
.close {
width: 100%;
margin-top: 40rpx;
display: flex;
justify-content: center;
align-items: center;
image {
width: 48rpx;
height: 48rpx;
}
}
.tag {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 5%;
left: 5%;
font-weight: 400;
font-size: 20rpx;
color: #fff;
box-sizing: border-box;
padding: 5rpx 15rpx;
border-radius: 25rpx;
}
// 售罄遮罩
.sold-out-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
border-radius: 16rpx 16rpx 0 0;
z-index: 10;
text {
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
}
// 加载中样式
.loading-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
.loading-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 20rpx;
image {
width: 100%;
height: 100%;
}
.loading-dot {
width: 40rpx;
height: 40rpx;
background-color: #f39205;
border-radius: 50%;
animation: bounce 1.5s infinite ease-in-out;
}
}
.loading-text {
font-size: 28rpx;
color: #333333;
padding: 10rpx 40rpx;
border-radius: 40rpx;
}
}
// 空状态样式
.empty-container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
border-radius: 16rpx;
.empty-text {
font-size: 28rpx;
color: #999999;
padding: 20rpx 40rpx;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 40rpx;
}
}
}
@keyframes bounce {
0%,
100% {
transform: scale(0);
opacity: 0.5;
}
50% {
transform: scale(1);
opacity: 1;
}
}
</style>