解决了加载商品时网页频繁闪烁的问题
This commit is contained in:
@@ -8,7 +8,16 @@
|
||||
<div class="products-container">
|
||||
<div class="product-card">
|
||||
<div class="product-image">
|
||||
<div v-if="loading || !firstProduct.images" class="carousel-skeleton">
|
||||
<div class="skeleton-image"></div>
|
||||
<div class="skeleton-indicators">
|
||||
<div class="skeleton-dot"></div>
|
||||
<div class="skeleton-dot"></div>
|
||||
<div class="skeleton-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
<el-carousel
|
||||
v-else
|
||||
:interval="4000"
|
||||
indicator-position="outside"
|
||||
style="min-height: 300px;"
|
||||
@@ -35,12 +44,20 @@
|
||||
<div v-for="product in products.filter(p => p.id !== firstProduct.id)" :key="product.id" class="product-card">
|
||||
<!-- 轮播图部分 -->
|
||||
<div class="product-image">
|
||||
<div v-if="!detailsLoaded" class="carousel-skeleton">
|
||||
<div class="skeleton-image"></div>
|
||||
<div class="skeleton-indicators">
|
||||
<div class="skeleton-dot"></div>
|
||||
<div class="skeleton-dot"></div>
|
||||
<div class="skeleton-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
<el-carousel
|
||||
v-else
|
||||
:interval="4000"
|
||||
indicator-position="outside"
|
||||
style="min-height: 300px;"
|
||||
>
|
||||
<!-- <el-carousel-item v-for="(image, index) in product.images" :key="index"> -->
|
||||
<el-carousel-item v-for="(image, index) in (productDetailsCache[product.id]?.images || [])" :key="index">
|
||||
<img :src="image" :alt="product.name" class="carousel-image" />
|
||||
</el-carousel-item>
|
||||
@@ -74,6 +91,8 @@ const products = ref([])
|
||||
const firstProduct = ref([])
|
||||
const productDetail = ref([])
|
||||
const productDetailsCache = ref({}) // 缓存所有商品详情
|
||||
const loading = ref(true) // 添加加载状态
|
||||
const detailsLoaded = ref(false) // 商品详情是否加载完成
|
||||
|
||||
const route = useRoute()
|
||||
const productId = ref(null)
|
||||
@@ -130,6 +149,8 @@ const getFirstProduct = async () => {
|
||||
} catch (error) {
|
||||
ElMessage.error('获取商品详情失败')
|
||||
console.log(error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,21 +180,44 @@ const getProductDetail = async (productId) => {
|
||||
const loadAllProductDetails = async () => {
|
||||
const uniqueProductIds = [...new Set(products.value.map(p => p.id))]
|
||||
|
||||
for (const id of uniqueProductIds) {
|
||||
// 并行加载所有商品详情,避免逐个加载时的多次渲染
|
||||
const loadPromises = uniqueProductIds.map(async (id) => {
|
||||
try {
|
||||
await getProductDetail(id)
|
||||
const response = await api.get(`/products/${id}`)
|
||||
if (response.data && response.data.data && response.data.data.product) {
|
||||
return { id, product: response.data.data.product }
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load details for product ${id}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 等待所有请求完成
|
||||
const results = await Promise.all(loadPromises)
|
||||
|
||||
// 一次性更新缓存,避免多次触发响应式更新
|
||||
const newCache = { ...productDetailsCache.value }
|
||||
results.forEach(result => {
|
||||
if (result) {
|
||||
newCache[result.id] = result.product
|
||||
}
|
||||
})
|
||||
|
||||
// 一次性更新缓存和加载状态
|
||||
productDetailsCache.value = newCache
|
||||
detailsLoaded.value = true
|
||||
}
|
||||
|
||||
// 添加生命周期钩子来调用函数
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
productId.value = route.params.id
|
||||
console.log('Product ID:', productId.value)
|
||||
getProducts()
|
||||
getFirstProduct()
|
||||
loading.value = true
|
||||
|
||||
// 先加载第一个商品,再加载商品列表,确保数据同步
|
||||
await getFirstProduct()
|
||||
await getProducts()
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -351,6 +395,71 @@ onMounted(() => {
|
||||
}
|
||||
/* 修改部分结束 */
|
||||
|
||||
/* 骨架屏样式 */
|
||||
.carousel-skeleton {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skeleton-image {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.skeleton-indicators {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #d0d0d0;
|
||||
animation: skeleton-pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.skeleton-dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.skeleton-dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton-pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/* 轮播图样式调整 */
|
||||
:deep(.el-carousel) {
|
||||
border-radius: 8px;
|
||||
|
||||
Reference in New Issue
Block a user