Files
RentWeAppFront/components/gallery/AssetGallery.vue

296 lines
6.1 KiB
Vue
Raw Normal View History

2025-12-25 08:26:09 +08:00
<template>
<view class="house-gallery">
<!-- 图片轮播 -->
<u-swiper
:list="swiperList"
:current="current"
height="1200rpx"
>
<template #default="{ item, index }">
<view class="slide-item">
<image :src="item.url" mode="aspectFill" class="slide-img" />
<!-- VR 按钮 -->
<view v-if="item.type === 'vr'" class="vr-btn" @click.stop="openVR">
VR
</view>
<!-- 视频按钮 -->
<view v-if="item.type === 'video'" class="video-btn" @click.stop="playVideo">
</view>
<!-- 图片索引 -->
<view class="slide-index">{{ index + 1 }}/{{ swiperList.length }}</view>
</view>
</template>
</u-swiper>
<!-- 广告遮罩栏 -->
<view v-if="adVisible && adImage" class="ad-mask" @click="onAdClick">
<image :src="adImage" mode="aspectFill" class="ad-img" />
<view class="ad-close" @click.stop="adVisible = false">×</view>
</view>
<!-- 底部 Tab -->
<view class="gallery-tab">
<view
class="gallery-tab-item"
v-for="(item, i) in tabs"
:key="i"
:id="'tab-' + i"
:class="{ active: activeTab === i }"
@click="onTabClick(i)"
>
{{ item.label }}
<text v-if="item.count">({{ item.count }})</text>
</view>
<!-- 滑块动画 -->
<view
class="tab-line"
:style="{ width: lineStyle.width + 'px', left: lineStyle.left + 'px' }"
></view>
</view>
<!-- 视频弹窗 -->
<u-popup v-model="videoVisible" mode="center" border-radius="20">
<video
:src="videoUrl"
autoplay
controls
style="width: 600rpx; height: 400rpx;"
></video>
</u-popup>
</view>
</template>
<script>
export default {
name: "AssetGallery",
props: {
vrImage: String,
videoCover: String,
videoUrl: String,
insideImages: Array,
planImages: Array,
adImage: String
},
data() {
return {
current: 0,
videoVisible: false,
adVisible: true,
tabs: [],
indexMap: [],
lineStyle: { width: 0, left: 0 }
};
},
mounted() {
this.$nextTick(() => {
setTimeout(() => this.updateTabLine(), 50);
});
},
watch: {
activeTab() {
this.$nextTick(() => this.updateTabLine());
}
},
computed: {
swiperList() {
let list = [];
this.tabs = [];
this.indexMap = [];
let idx = 0;
if (this.vrImage) {
this.tabs.push({ key: "vr", label: "VR", count: 1 });
this.indexMap.push(idx);
list.push({ type: "vr", url: this.vrImage });
idx++;
}
if (this.videoCover) {
this.tabs.push({ key: "video", label: "视频", count: 1 });
this.indexMap.push(idx);
list.push({ type: "video", url: this.videoCover });
idx++;
}
if (this.insideImages?.length > 0) {
this.tabs.push({ key: "inside", label: "内部图", count: this.insideImages.length });
this.indexMap.push(idx);
list.push(...this.insideImages.map((img) => ({ type: "inside", img })));
idx += this.insideImages.length;
}
if (this.planImages?.length > 0) {
this.tabs.push({ key: "plan", label: "平面图", count: this.planImages.length });
this.indexMap.push(idx);
list.push(...this.planImages.map((url) => ({ type: "plan", url })));
idx += this.planImages.length;
}
return list;
},
activeTab() {
for (let i = 0; i < this.indexMap.length; i++) {
const start = this.indexMap[i];
const end = this.indexMap[i + 1] ?? 99999;
if (this.current >= start && this.current < end) return i;
}
return 0;
}
},
methods: {
onAdClick() {
this.$emit("ad-click");
},
onSwiperChange(e) {
this.current = e.detail.current;
},
onTabClick(i) {
this.current = this.indexMap[i];
},
openVR() {
this.$emit("open-vr");
},
playVideo() {
this.videoVisible = true;
},
updateTabLine() {
const index = this.activeTab;
if (this.tabs.length === 0) return;
const id = `#tab-${index}`;
uni.createSelectorQuery()
.in(this)
.select(id)
.boundingClientRect((rect) => {
if (!rect) return;
this.lineStyle = { width: rect.width, left: rect.left };
})
.exec();
}
}
};
</script>
<style scoped>
.house-gallery {
width: 100%;
position: relative;
}
/* 轮播 */
.slide-item {
width: 100%;
height: 1200rpx;
position: relative;
}
.slide-img {
width: 100%;
height: 100%;
border-radius: 0;
}
.vr-btn,
.video-btn {
position: absolute;
bottom: 40rpx;
right: 40rpx;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
}
.video-btn {
font-size: 50rpx;
}
.slide-index {
position: absolute;
bottom: 20rpx;
right: 20rpx;
padding: 4rpx 8rpx;
background: rgba(0,0,0,0.5);
color: #fff;
font-size: 24rpx;
border-radius: 10rpx;
}
/* 广告遮罩 */
.ad-mask {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 120rpx;
background: rgba(0,0,0,0.35);
padding: 0 20rpx;
z-index: 9;
}
.ad-img {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.ad-close {
position: absolute;
right: 20rpx;
top: 20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: rgba(0,0,0,0.55);
color: #fff;
text-align: center;
line-height: 40rpx;
font-size: 30rpx;
}
/* Tab bar */
.gallery-tab {
position: absolute;
bottom: 0;
width: 100%;
display: flex;
background: rgba(255,255,255,0.9);
border-top: 1rpx solid #eee;
height: 90rpx;
align-items: center;
justify-content: space-around;
z-index: 10;
}
.gallery-tab-item {
padding: 20rpx 0;
font-size: 28rpx;
color: #666;
}
.gallery-tab-item.active {
color: #2979ff;
font-weight: bold;
}
.tab-line {
position: absolute;
bottom: 0;
height: 4rpx;
background: #2979ff;
transition: all 0.25s ease;
}
</style>