liveh5-nuxt/app/pages/home/components/ItemList/index.vue
xingyy 4e771fde21 feat(app): 新增商品详情组件和瀑布流列表
- 添加了 itemDetail 组件,用于展示商品详细信息
- 在 home 页面中集成了瀑布流列表组件
- 实现了商品列表的加载更多和下拉刷新功能
- 添加了商品详情弹窗组件
2025-03-06 15:49:31 +08:00

209 lines
6.2 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.

<script setup>
import { ref, computed } from 'vue'
import { goodStore } from "@/stores/goods"
import DetailPopup from '../DetailPopup/index.vue'
import WaterfallFlow from '@/components/waterfallFlow/index.vue'
const {
itemList,
pageRef,
currentItem,
loading: storeLoading,
getArtworkList,
} = goodStore()
const localState = ref({
finished: false,
refreshing: false,
showDetail: false,
showHeight: ''
})
// 记录每个图片的加载状态
const imageLoadingStatus = ref({})
// 生成骨架屏列表数据
const skeletonList = computed(() => {
// 如果有实际数据,返回空数组
if (itemList.value && itemList.value.length > 0) return []
// 否则返回6个骨架屏项目
return Array(6).fill().map((_, index) => ({
id: `skeleton-${index}`,
isSkeletonItem: true
}))
})
// 合并实际列表和骨架屏列表
const displayList = computed(() => {
if (itemList.value && itemList.value.length > 0) return itemList.value
return skeletonList.value
})
// 图片加载完成处理函数
const handleImageLoad = (itemId) => {
imageLoadingStatus.value[itemId] = true
}
// 图片加载错误处理函数
const handleImageError = (itemId) => {
imageLoadingStatus.value[itemId] = true // 也标记为加载完成,避免一直显示骨架屏
}
// 加载更多
const loadMore = async () => {
pageRef.value.page++
const { finished } = await getArtworkList()
localState.value.finished = finished
}
// 刷新
const onRefresh = async () => {
try {
localState.value.refreshing = true
localState.value.finished = false
// 重置图片加载状态
imageLoadingStatus.value = {}
const { finished } = await getArtworkList(true)
localState.value.finished = finished
} finally {
localState.value.refreshing = false
}
}
// 打开详情
const openShow = async (item) => {
if (item.isSkeletonItem) return
localState.value.showDetail = true
currentItem.value = item
}
</script>
<template>
<div class="px-[16px] pt-[16px]">
<van-pull-refresh
v-model="localState.refreshing"
:success-duration="700"
@refresh="onRefresh"
>
<template #success>
<van-icon name="success" /> <span>{{ $t('home.refresh_show') }}</span>
</template>
<van-list
v-model:loading="storeLoading"
:finished="localState.finished"
:finished-text="$t('home.finished_text')"
@load="loadMore"
>
<div class="w-full flex gap-[16px]">
<WaterfallFlow :items="displayList" :column-count="2">
<template #default="{ item }">
<div
@click="openShow(item)"
class="w-full"
>
<div class="relative w-full">
<!-- 自定义骨架屏 -->
<div
v-if="item.isSkeletonItem || !imageLoadingStatus[item.id]"
class="custom-skeleton rounded-4px overflow-hidden"
>
<div class="skeleton-image"></div>
<div class="skeleton-content">
<div class="skeleton-title"></div>
<div class="skeleton-text"></div>
<div class="skeleton-text"></div>
</div>
</div>
<template v-if="!item.isSkeletonItem">
<img
:src="item.artwork?.hdPic"
class="w-full object-cover rounded-4px"
:class="{'hidden': !imageLoadingStatus[item.id]}"
@load="handleImageLoad(item.id)"
@error="handleImageError(item.id)"
/>
<div
class="absolute rounded-2px overflow-hidden line-height-12px left-[8px] top-[8px] h-[17px] w-[60px] flex items-center justify-center bg-[#2b53ac] text-[12px] text-[#fff]"
:class="{'z-10': !imageLoadingStatus[item.id]}"
>
Lot{{item.index+25000 }}
</div>
</template>
</div>
<div v-if="!item.isSkeletonItem" class="pt-[8px]" :class="{'hidden': !imageLoadingStatus[item.id]}">
<div class="text-[14px] text-[#000000] leading-[20px]">
{{ item?.artwork?.name }} | {{item?.artwork?.artistName}}
</div>
<div class="mt-[4px] text-[12px] text-[#575757]">
{{ $t('home.start_price') }}{{ item?.startPrice??0 }}
</div>
<div
v-if="item.soldPrice"
class="mt-[4px] text-[12px] text-[#b58047]"
>
{{ $t('home.close_price') }}{{ item?.soldPrice??0 }}
</div>
</div>
</div>
</template>
</WaterfallFlow>
</div>
</van-list>
</van-pull-refresh>
<DetailPopup v-model:show="localState.showDetail" :detailInfo="currentItem"></DetailPopup>
</div>
</template>
<style scoped>
.content {
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 自定义骨架屏样式 */
.custom-skeleton {
width: 100%;
display: flex;
flex-direction: column;
background-color: #fff;
}
.skeleton-image {
width: 100%;
padding-bottom: 100%; /* 保持1:1的宽高比 */
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: skeleton-loading 1.4s ease infinite;
}
.skeleton-content {
padding: 8px 0;
}
.skeleton-title {
height: 16px;
margin-bottom: 8px;
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: skeleton-loading 1.4s ease infinite;
border-radius: 2px;
}
.skeleton-text {
height: 12px;
margin-top: 4px;
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: skeleton-loading 1.4s ease infinite;
border-radius: 2px;
width: 60%;
}
@keyframes skeleton-loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
</style>