Files
uniapp-im-shop/uni_modules/tuikit-atomic-x/components/GiftPicker.nvue
2026-01-12 17:52:15 +08:00

320 lines
8.1 KiB
Plaintext

<template>
<view class="bottom-drawer-container" v-if="modelValue">
<view class="drawer-overlay" @tap="close"></view>
<view class="bottom-drawer" :class="{ 'drawer-open': modelValue }">
<view class="gift-header">
<view class="header-content">
<text class="gift-title">礼物</text>
</view>
</view>
<swiper class="gift-content" :current="currentPage" @change="handleSwiperChange" style="height: 710rpx;">
<swiper-item v-for="(page, pageIndex) in giftPages" :key="pageIndex" class="gift-page">
<view class="gift-container">
<view class="gift-item" v-for="(giftInfo, index) in page" :key="giftInfo.giftID"
:class="{ 'selected': selectedGiftIndex === (pageIndex * itemsPerPage + index) }"
@tap="selectGift(pageIndex * itemsPerPage + index)">
<view class="gift-image-container">
<image class="gift-image" :src="giftInfo.iconURL" mode="aspectFit" />
</view>
<view class="gift-action" v-if="selectedGiftIndex === (pageIndex * itemsPerPage + index)">
<view class="send-btn selected-btn" @tap.stop="handleSendGift(pageIndex * itemsPerPage + index)">
<text class="send-text">赠送</text>
</view>
</view>
<text
class="gift-name">{{ selectedGiftIndex === (pageIndex * itemsPerPage + index) ? '' : (giftInfo.name || '') }}</text>
<text class="gift-price">{{ giftInfo.coins }}</text>
</view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script>
import {
ref
} from 'vue';
import {
downloadAndSaveToPath
} from '@/uni_modules/tuikit-atomic-x/components/GiftPlayer/giftService';
import {
useGiftState
} from "@/uni_modules/tuikit-atomic-x/state/GiftState";
const {
usableGifts,
latestGift,
sendGift,
refreshUsableGifts
} = useGiftState(uni?.$liveID);
export default {
name: 'GiftPanel',
props: {
modelValue: {
type: Boolean,
default: false
},
onGiftSelect: {
type: Function,
default: null
},
},
data() {
return {
scrollTop: 0,
selectedGiftIndex: 0,
giftLists: usableGifts,
currentPage: 0,
itemsPerPage: 8,
};
},
methods: {
close() {
this.$emit('update:modelValue', false);
},
handleSwiperChange(e) {
this.currentPage = e.detail.current;
},
selectGift(index) {
this.selectedGiftIndex = index;
},
handleSendGift(index) {
const gift = (this.flattenedGifts || [])[index];
if (this.selectedGiftIndex !== index) return;
if (this.onGiftSelect) {
this.onGiftSelect(gift);
}
this.selectedGiftIndex = -1;
},
handleRecharge() {
this.$emit('recharge');
}
},
computed: {
// 兼容新的分类结构与旧的扁平结构
flattenedGifts() {
const list = this.giftLists || [];
if (!Array.isArray(list)) return [];
// 新结构:[{ categoryID, name, giftList: [...] }, ...]
if (list.length > 0 && list[0] && Array.isArray(list[0].giftList)) {
const merged = [];
for (let i = 0; i < list.length; i++) {
const category = list[i];
const gifts = Array.isArray(category.giftList) ? category.giftList : [];
for (let j = 0; j < gifts.length; j++) {
merged.push(gifts[j]);
}
}
return merged;
}
// 旧结构:直接为礼物数组
return list;
},
giftPages() {
const pages = [];
const list = this.flattenedGifts;
for (let i = 0; i < list.length; i += this.itemsPerPage) {
pages.push(list.slice(i, i + this.itemsPerPage));
}
return pages;
}
},
watch: {
async giftLists(newVal) {
const flatten = () => {
if (!Array.isArray(newVal)) return [];
if (newVal.length > 0 && newVal[0] && Array.isArray(newVal[0].giftList)) {
// 分类结构
const out = [];
for (let i = 0; i < newVal.length; i++) {
const gifts = Array.isArray(newVal[i].giftList) ? newVal[i].giftList : [];
for (let j = 0; j < gifts.length; j++) out.push(gifts[j]);
}
return out;
}
return newVal;
};
const flatList = flatten();
if (!flatList || flatList.length === 0) return;
for (let i = 0; i < flatList.length; i++) {
const giftData = flatList[i];
if (giftData && giftData.resourceURL) {
const giftKey = `${(giftData.name || '').split(' ').join('')}-${giftData.giftID}`;
if (plus && plus.storage && plus.storage.getAllKeys && plus.storage.getAllKeys().includes(giftKey)) continue;
const svgaGiftSourceUrl = plus && plus.storage ? plus.storage.getItem(giftKey) : null;
if (!svgaGiftSourceUrl) {
const filePath = await downloadAndSaveToPath(`${giftData.resourceURL}`);
if (plus && plus.storage) plus.storage.setItem(giftKey, filePath);
}
}
}
}
},
mounted() {
if (!uni?.$liveID) {
return;
}
refreshUsableGifts({
liveID: uni.$liveID
})
},
};
</script>
<style>
.bottom-drawer-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
top: 0;
z-index: 1000;
}
.drawer-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
}
.bottom-drawer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(34, 38, 46, 1);
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
transform: translateY(100%);
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease;
flex-direction: column;
height: 710rpx;
}
.drawer-open {
transform: translateY(0);
}
.gift-header {
padding: 40rpx 48rpx;
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
position: relative;
z-index: 1;
}
.header-content {
flex-direction: row;
justify-content: center;
align-items: center;
}
.gift-title {
font-size: 36rpx;
color: #ffffff;
font-weight: 600;
}
.gift-content {
flex: 1;
height: 710rpx;
}
.gift-page {
flex: 1;
height: 710rpx;
}
.gift-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 0 20rpx;
flex: 1;
height: 710rpx;
}
.gift-item {
width: 168rpx;
/* 4列布局 */
margin-bottom: 24rpx;
align-items: center;
border-radius: 20rpx;
padding: 10rpx 6rpx 6rpx 6rpx;
border: 2rpx solid transparent;
background-color: transparent;
box-sizing: border-box;
height: 230rpx;
}
.gift-item.selected {
border-color: #2B6AD6;
background-color: rgba(43, 106, 214, 0.12);
height: 230rpx;
}
.gift-image-container {
width: 110rpx;
height: 110rpx;
margin-bottom: 12rpx;
justify-content: center;
align-items: center;
}
.gift-image {
width: 110rpx;
height: 110rpx;
}
.gift-action {
height: 56rpx;
/* 固定高度避免布局抖动 */
margin-bottom: 8rpx;
justify-content: center;
align-items: center;
}
.send-btn {
padding: 8rpx 24rpx;
border-radius: 100rpx;
background-color: rgba(58, 60, 66, 1);
justify-content: center;
align-items: center;
}
.send-btn.selected-btn {
background-color: #2b6ad6;
}
.send-text {
color: #ffffff;
font-size: 24rpx;
}
/* 名称与价格样式 */
.gift-name {
color: #ffffff;
font-size: 26rpx;
line-height: 36rpx;
text-align: center;
max-width: 150rpx;
lines: 1;
text-overflow: ellipsis;
overflow: hidden;
}
.gift-price {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.7);
line-height: 28rpx;
text-align: center;
}
</style>