添加首页加载skeleton
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<!-- pages/main/index.uvue -->
|
||||
<template>
|
||||
<view class="medic-home">
|
||||
<view :class="['home-main-stage', pageLoading ? 'home-main-stage-hidden' : 'home-main-stage-visible']">
|
||||
<view class="jd-header-fixed">
|
||||
<JdLikeHomeHeader
|
||||
:status-bar-height="statusBarHeight"
|
||||
@@ -59,6 +60,7 @@
|
||||
<HomeMallContent
|
||||
v-if="activeTopModule == 'home'"
|
||||
:current-category="currentCategory"
|
||||
:page-loading="pageLoading"
|
||||
:selected-sub-category-id="selectedSubCategoryId"
|
||||
:secondary-category-display="secondaryCategoryDisplay"
|
||||
:marketing-channels="marketingChannels"
|
||||
@@ -220,6 +222,22 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showHomeSkeleton"
|
||||
:class="['home-skeleton-overlay', pageLoading ? 'home-skeleton-overlay-visible' : 'home-skeleton-overlay-leaving']"
|
||||
>
|
||||
<HomeSkeleton
|
||||
:status-bar-height="statusBarHeight"
|
||||
:capsule-right="navBarRight"
|
||||
:bottom-safe-area="bottomSafeArea"
|
||||
:show-category-bar="true"
|
||||
:category-count="7"
|
||||
:channel-count="4"
|
||||
:product-count="8"
|
||||
></HomeSkeleton>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -228,6 +246,7 @@ import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { onShow, onLoad, onHide } from '@dcloudio/uni-app'
|
||||
import HomeMallContent from '@/components/home/HomeMallContent.uvue'
|
||||
import JdLikeHomeHeader from '@/components/home/JdLikeHomeHeader.uvue'
|
||||
import HomeSkeleton from '@/components/home-skeleton/home-skeleton.uvue'
|
||||
import { fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||
import supabaseService from '@/utils/supabaseService.uts'
|
||||
@@ -246,6 +265,11 @@ const refreshing = ref(false)
|
||||
const loading = ref(false)
|
||||
const isFirstShow = ref(true)
|
||||
const hasMore = ref(true)
|
||||
const pageLoading = ref(true)
|
||||
const goodsLoaded = ref(false)
|
||||
const categoryLoaded = ref(false)
|
||||
const bannerLoaded = ref(false)
|
||||
const skeletonExiting = ref(false)
|
||||
const activeSort = ref('recommend') // 默认展示智能推荐
|
||||
const activeFilter = ref('recommend')
|
||||
const currentPage = ref(1)
|
||||
@@ -598,6 +622,11 @@ const nextPlaceholderKeyword = ref(placeholderKeywords.value.length > 1 ? placeh
|
||||
const placeholderAnimating = ref(false)
|
||||
let placeholderTimer: number = 0
|
||||
let placeholderAnimationTimer: number = 0
|
||||
let skeletonTimer: number = 0
|
||||
|
||||
const showHomeSkeleton = computed((): boolean => {
|
||||
return pageLoading.value || skeletonExiting.value
|
||||
})
|
||||
|
||||
const homeEmptyStateTitle = computed((): string => {
|
||||
if (currentFeedCategoryId.value == 'recommend') {
|
||||
@@ -660,6 +689,42 @@ function stopPlaceholderScroll(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function resetInitialHomeLoadFlags(): void {
|
||||
goodsLoaded.value = false
|
||||
categoryLoaded.value = false
|
||||
bannerLoaded.value = false
|
||||
}
|
||||
|
||||
function clearSkeletonTimer(): void {
|
||||
if (skeletonTimer != 0) {
|
||||
clearTimeout(skeletonTimer)
|
||||
skeletonTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function beginHomeSkeleton(): void {
|
||||
clearSkeletonTimer()
|
||||
pageLoading.value = true
|
||||
skeletonExiting.value = false
|
||||
}
|
||||
|
||||
function finishHomeSkeleton(): void {
|
||||
if (!pageLoading.value) {
|
||||
return
|
||||
}
|
||||
pageLoading.value = false
|
||||
skeletonExiting.value = true
|
||||
clearSkeletonTimer()
|
||||
skeletonTimer = setTimeout(() => {
|
||||
skeletonExiting.value = false
|
||||
skeletonTimer = 0
|
||||
}, 280)
|
||||
}
|
||||
|
||||
async function loadHomeBannerData(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
function startPlaceholderScroll(): void {
|
||||
try {
|
||||
if (placeholderTimer != 0) {
|
||||
@@ -1910,9 +1975,17 @@ const searchByKeyword = (keyword: string): void => {
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
type InitDataOptions = {
|
||||
showSkeleton: boolean
|
||||
}
|
||||
|
||||
const initData = async (options: InitDataOptions = { showSkeleton: false }) => {
|
||||
logSupaConfig()
|
||||
console.log('[consumer-db] 首页开始加载数据')
|
||||
resetInitialHomeLoadFlags()
|
||||
if (options.showSkeleton) {
|
||||
beginHomeSkeleton()
|
||||
}
|
||||
// 首先确保用户资料已加载
|
||||
try {
|
||||
await getCurrentUser()
|
||||
@@ -1920,16 +1993,50 @@ const initData = async () => {
|
||||
} catch (error) {
|
||||
console.error('加载用户资料失败:', error)
|
||||
}
|
||||
await loadMedicalMallCategories()
|
||||
await loadBrands()
|
||||
await loadHotKeywords()
|
||||
await loadServiceHomeData()
|
||||
if (await consumeSelectedCategoryFromStorage()) {
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
return
|
||||
const categoryTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await loadMedicalMallCategories()
|
||||
} finally {
|
||||
categoryLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const bannerTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await loadHomeBannerData()
|
||||
} finally {
|
||||
bannerLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const goodsTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await categoryTask
|
||||
if (!(await consumeSelectedCategoryFromStorage())) {
|
||||
await loadCategoryGoods(currentCategory.value)
|
||||
}
|
||||
} finally {
|
||||
goodsLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const supplementTask = (async (): Promise<void> => {
|
||||
await Promise.all([
|
||||
loadBrands(),
|
||||
loadHotKeywords(),
|
||||
loadServiceHomeData()
|
||||
])
|
||||
try {
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
} catch (error) {
|
||||
console.error('加载推荐频道商品失败:', error)
|
||||
recommendedProducts.value = []
|
||||
}
|
||||
})()
|
||||
try {
|
||||
await Promise.all([categoryTask, goodsTask, bannerTask, supplementTask])
|
||||
} finally {
|
||||
if (options.showSkeleton && categoryLoaded.value && goodsLoaded.value && bannerLoaded.value) {
|
||||
finishHomeSkeleton()
|
||||
}
|
||||
}
|
||||
await loadCategoryGoods(currentCategory.value)
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
}
|
||||
|
||||
|
||||
@@ -2035,7 +2142,7 @@ const initPage = () => {
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
initPage()
|
||||
initData()
|
||||
void initData({ showSkeleton: true })
|
||||
startPlaceholderScroll()
|
||||
})
|
||||
|
||||
@@ -2085,6 +2192,7 @@ onHide(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
stopPlaceholderScroll()
|
||||
clearSkeletonTimer()
|
||||
})
|
||||
|
||||
// 处理滚动事件
|
||||
@@ -2202,7 +2310,7 @@ const onRefresh = async () => {
|
||||
|
||||
try {
|
||||
// 重新加载数据
|
||||
await initData()
|
||||
await initData({ showSkeleton: false })
|
||||
} catch (e) {
|
||||
console.error('刷新数据失败:', e)
|
||||
} finally {
|
||||
@@ -2432,6 +2540,41 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-main-stage {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
transition: opacity 260ms ease;
|
||||
}
|
||||
|
||||
.home-main-stage-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.home-main-stage-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1200;
|
||||
background: #f6f8fb;
|
||||
transition: opacity 260ms ease;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay-leaving {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jd-header-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
Reference in New Issue
Block a user