解决了加载商品时网页频繁闪烁的问题
This commit is contained in:
@@ -8,7 +8,16 @@
|
|||||||
<div class="products-container">
|
<div class="products-container">
|
||||||
<div class="product-card">
|
<div class="product-card">
|
||||||
<div class="product-image">
|
<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
|
<el-carousel
|
||||||
|
v-else
|
||||||
:interval="4000"
|
:interval="4000"
|
||||||
indicator-position="outside"
|
indicator-position="outside"
|
||||||
style="min-height: 300px;"
|
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 v-for="product in products.filter(p => p.id !== firstProduct.id)" :key="product.id" class="product-card">
|
||||||
<!-- 轮播图部分 -->
|
<!-- 轮播图部分 -->
|
||||||
<div class="product-image">
|
<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
|
<el-carousel
|
||||||
|
v-else
|
||||||
:interval="4000"
|
:interval="4000"
|
||||||
indicator-position="outside"
|
indicator-position="outside"
|
||||||
style="min-height: 300px;"
|
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">
|
<el-carousel-item v-for="(image, index) in (productDetailsCache[product.id]?.images || [])" :key="index">
|
||||||
<img :src="image" :alt="product.name" class="carousel-image" />
|
<img :src="image" :alt="product.name" class="carousel-image" />
|
||||||
</el-carousel-item>
|
</el-carousel-item>
|
||||||
@@ -74,6 +91,8 @@ const products = ref([])
|
|||||||
const firstProduct = ref([])
|
const firstProduct = ref([])
|
||||||
const productDetail = ref([])
|
const productDetail = ref([])
|
||||||
const productDetailsCache = ref({}) // 缓存所有商品详情
|
const productDetailsCache = ref({}) // 缓存所有商品详情
|
||||||
|
const loading = ref(true) // 添加加载状态
|
||||||
|
const detailsLoaded = ref(false) // 商品详情是否加载完成
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const productId = ref(null)
|
const productId = ref(null)
|
||||||
@@ -130,6 +149,8 @@ const getFirstProduct = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('获取商品详情失败')
|
ElMessage.error('获取商品详情失败')
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,21 +180,44 @@ const getProductDetail = async (productId) => {
|
|||||||
const loadAllProductDetails = async () => {
|
const loadAllProductDetails = async () => {
|
||||||
const uniqueProductIds = [...new Set(products.value.map(p => p.id))]
|
const uniqueProductIds = [...new Set(products.value.map(p => p.id))]
|
||||||
|
|
||||||
for (const id of uniqueProductIds) {
|
// 并行加载所有商品详情,避免逐个加载时的多次渲染
|
||||||
|
const loadPromises = uniqueProductIds.map(async (id) => {
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
console.warn(`Failed to load details for product ${id}:`, 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
|
productId.value = route.params.id
|
||||||
console.log('Product ID:', productId.value)
|
console.log('Product ID:', productId.value)
|
||||||
getProducts()
|
loading.value = true
|
||||||
getFirstProduct()
|
|
||||||
|
// 先加载第一个商品,再加载商品列表,确保数据同步
|
||||||
|
await getFirstProduct()
|
||||||
|
await getProducts()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</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) {
|
:deep(.el-carousel) {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user