229 lines
5.4 KiB
Vue
229 lines
5.4 KiB
Vue
<template>
|
||
<view v-if="visible" class="cabinet-overlay" @touchmove.stop.prevent>
|
||
<view class="cabinet-mask" @tap="close"></view>
|
||
<view class="cabinet-panel" @tap.stop>
|
||
<view class="cabinet-header">
|
||
<text class="cabinet-title">我的盒柜</text>
|
||
<view class="cabinet-actions">
|
||
<text class="view-all" @tap="goFullCabinet">查看全部</text>
|
||
<text class="cabinet-close" @tap="close">×</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading" class="cabinet-loading">
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
|
||
<view v-else-if="items.length === 0" class="cabinet-empty">
|
||
<text class="empty-text">暂无物品,参与活动获取奖品</text>
|
||
</view>
|
||
|
||
<scroll-view v-else scroll-x class="cabinet-scroll">
|
||
<view class="thumb-list">
|
||
<view v-for="item in displayItems" :key="item.id" class="thumb-item">
|
||
<image class="thumb-img" :src="item.image" mode="aspectFill" />
|
||
<text class="thumb-count">x{{ item.count }}</text>
|
||
</view>
|
||
<view v-if="hasMore" class="thumb-more" @tap="goFullCabinet">
|
||
<text>+{{ items.length - maxDisplay }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch } from 'vue'
|
||
import { getInventory } from '@/api/appUser'
|
||
|
||
const props = defineProps({
|
||
visible: { type: Boolean, default: false },
|
||
activityId: { type: [String, Number], default: '' }
|
||
})
|
||
|
||
const emit = defineEmits(['update:visible'])
|
||
|
||
const loading = ref(false)
|
||
const items = ref([])
|
||
const total = ref(0)
|
||
const maxDisplay = 8
|
||
|
||
const displayItems = computed(() => items.value.slice(0, maxDisplay))
|
||
const hasMore = computed(() => items.value.length > maxDisplay)
|
||
|
||
function close() { emit('update:visible', false) }
|
||
|
||
function goFullCabinet() {
|
||
close()
|
||
uni.switchTab({ url: '/pages/cabinet/index' })
|
||
}
|
||
|
||
function cleanUrl(u) {
|
||
if (!u) return '/static/logo.png'
|
||
let s = String(u).trim()
|
||
if (s.startsWith('[') && s.endsWith(']')) {
|
||
try { const arr = JSON.parse(s); if (Array.isArray(arr) && arr.length > 0) s = arr[0] } catch (e) {}
|
||
}
|
||
s = s.replace(/[`'"]/g, '').trim()
|
||
const m = s.match(/https?:\/\/[^\s]+/)
|
||
if (m && m[0]) return m[0]
|
||
return s || '/static/logo.png'
|
||
}
|
||
|
||
async function loadItems() {
|
||
loading.value = true
|
||
try {
|
||
const userId = uni.getStorageSync('user_id')
|
||
if (!userId) { items.value = []; total.value = 0; return }
|
||
const res = await getInventory(userId, 1, 50, { status: 1 })
|
||
let list = []
|
||
let rawTotal = 0
|
||
if (res && Array.isArray(res.list)) { list = res.list; rawTotal = res.total || 0 }
|
||
else if (res && Array.isArray(res.data)) { list = res.data; rawTotal = res.total || 0 }
|
||
else if (Array.isArray(res)) { list = res; rawTotal = res.length }
|
||
|
||
// 后端已按 status=1 过滤,这里只需要排除前端正在处理的项
|
||
// 后端已经按 status=1 过滤并聚合,直接映射
|
||
const displayRes = list.map(item => ({
|
||
id: item.product_id,
|
||
name: (item.product_name || '未知商品').trim(),
|
||
image: cleanUrl(item.product_images || item.image),
|
||
count: item.count
|
||
}))
|
||
items.value = displayRes
|
||
total.value = rawTotal
|
||
} catch (e) {
|
||
console.error('[CabinetPreviewPopup] 加载失败', e)
|
||
items.value = []
|
||
total.value = 0
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
watch(() => props.visible, (v) => { if (v) loadItems() })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.cabinet-overlay {
|
||
position: fixed;
|
||
left: 0; right: 0; top: 0; bottom: 0;
|
||
z-index: 9000;
|
||
}
|
||
|
||
.cabinet-mask {
|
||
position: absolute;
|
||
left: 0; right: 0; top: 0; bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.cabinet-panel {
|
||
position: absolute;
|
||
left: 24rpx; right: 24rpx;
|
||
bottom: calc(env(safe-area-inset-bottom) + 24rpx);
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-radius: 24rpx;
|
||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||
overflow: hidden;
|
||
animation: slideUp 0.2s ease-out;
|
||
}
|
||
|
||
.cabinet-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 24rpx;
|
||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.cabinet-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
}
|
||
|
||
.cabinet-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.view-all {
|
||
font-size: 24rpx;
|
||
color: #FF6B35;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.cabinet-close {
|
||
font-size: 40rpx;
|
||
line-height: 1;
|
||
color: #999;
|
||
padding: 0 8rpx;
|
||
}
|
||
|
||
.cabinet-loading, .cabinet-empty {
|
||
padding: 32rpx 24rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.loading-text, .empty-text {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.cabinet-scroll {
|
||
white-space: nowrap;
|
||
padding: 20rpx 24rpx;
|
||
}
|
||
|
||
.thumb-list {
|
||
display: inline-flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.thumb-item {
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.thumb-img {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 12rpx;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.thumb-count {
|
||
position: absolute;
|
||
right: 4rpx;
|
||
bottom: 4rpx;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
color: #fff;
|
||
font-size: 20rpx;
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.thumb-more {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 12rpx;
|
||
background: #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from { transform: translateY(20rpx); opacity: 0; }
|
||
to { transform: translateY(0); opacity: 1; }
|
||
}
|
||
</style>
|
||
|