feat:增加骨架屏加载样式

This commit is contained in:
zoujing
2026-09-20 10:07:54 +08:00
parent cdb485afd2
commit 3f6b427f2f
4 changed files with 103 additions and 5 deletions

View File

@@ -42,6 +42,8 @@
--color-line2: #e4e8ec;
--color-alt: #f5f7f9;
--color-danger: #e5484d;
/* 骨架屏占位块填充(配 animate-pulse 使用) */
--color-skel: rgba(20, 24, 28, 0.08);
/* ---- 字阶(设计稿 7 级;零散字号用 text-[13.5px] 这类任意值) ---- */
--text-hint: 10.5px; /* 弱提示 */

View File

@@ -14,7 +14,11 @@ defineProps({
<template>
<view class="mt-[26px] mx-[14px] px-5 py-[54px] text-center">
<AppIcon v-if="showIcon" :name="icon" tone="ink3" :size="34" />
<!-- 组件宿主节点是行内盒图标根节点却是 display:block 的定宽块
直接放 text-center 里不会居中会贴左 flex 容器托管才能可靠居中 -->
<view v-if="showIcon" class="flex justify-center">
<AppIcon :name="icon" tone="ink3" :size="34" />
</view>
<view class="mt-3 text-[13px] text-ink3">{{ text }}</view>
<slot />
</view>

View File

@@ -57,6 +57,10 @@ const subTabs = computed(() =>
BENEFIT_SUB_TABS.map((t) => ({ ...t, count: counts.value[t.key] ?? 0 }))
);
/** 骨架屏开关:对应列表还没从服务端拉过(加载完才允许出空状态) */
const centerPending = computed(() => !store.centerLoaded);
const minePending = computed(() => !!getAccessToken() && !store.mineLoaded);
/** 壳在「切到本面板」与「页面 onShow」时调 */
const onPanelShow = () => {
// seg=mine 这类「切过来要落在哪个分段」的意图走 nav 的内存透传(不经过路由,故不用 query
@@ -231,17 +235,48 @@ const onConfirmUse = async () => {
<template v-if="seg === 'center'">
<!-- -mt-[7px] 抵掉 BenefitCard 自带 mt-[11px]分类栏下方间距 11px **4px** -->
<view class="px-[14px] -mt-[7px]">
<BenefitCard v-for="item in centerList" :key="item.id" :benefit="item" mode="center" @action="onClaim" />
<template v-if="centerPending">
<!-- 首屏骨架3 张卡占位结构与 BenefitCard 一致 -->
<view v-for="i in 3" :key="i"
class="relative flex h-[130px] bg-white border-[0.5px] border-line rounded-card mt-[11px] overflow-hidden">
<view class="flex-1 min-w-0 px-[14px] pt-[13px] pb-3 flex flex-col">
<view class="h-[13px] w-[64px] rounded-[7px] animate-pulse bg-skel" />
<view class="mt-2 h-[26px] w-[108px] rounded-[8px] animate-pulse bg-skel" />
<view class="mt-[9px] h-[11px] w-[74%] rounded-[6px] animate-pulse bg-skel" />
<view class="mt-auto h-[10px] w-[116px] rounded-[5px] animate-pulse bg-skel" />
</view>
<view
class="w-[38%] min-w-[112px] shrink-0 border-l border-dashed border-[rgba(255,138,61,0.45)] animate-pulse bg-skel" />
</view>
</template>
<template v-else>
<BenefitCard v-for="item in centerList" :key="item.id" :benefit="item" mode="center" @action="onClaim" />
</template>
</view>
<EmptyBox v-if="!centerList.length" text="该类别暂无可用权益" />
<EmptyBox v-if="!centerPending && !centerList.length" text="该类别暂无可用权益" />
</template>
<!-- 分段二 · 我的权益 -->
<template v-else>
<view class="px-[14px]">
<BenefitCard v-for="item in mineList" :key="item.id" :benefit="item" mode="mine" @action="onCardTap" />
<template v-if="minePending">
<view v-for="i in 3" :key="i"
class="relative flex h-[130px] bg-white border-[0.5px] border-line rounded-card mt-[11px] overflow-hidden">
<view class="flex-1 min-w-0 px-[14px] pt-[13px] pb-3 flex flex-col">
<view class="h-[13px] w-[64px] rounded-[7px] animate-pulse bg-skel" />
<view class="mt-2 h-[26px] w-[108px] rounded-[8px] animate-pulse bg-skel" />
<view class="mt-[9px] h-[11px] w-[74%] rounded-[6px] animate-pulse bg-skel" />
<view class="mt-auto h-[10px] w-[116px] rounded-[5px] animate-pulse bg-skel" />
</view>
<view
class="w-[38%] min-w-[112px] shrink-0 border-l border-dashed border-[rgba(255,138,61,0.45)] animate-pulse bg-skel" />
</view>
</template>
<template v-else>
<BenefitCard v-for="item in mineList" :key="item.id" :benefit="item" mode="mine" @action="onCardTap" />
</template>
</view>
<EmptyBox v-if="!mineList.length" text="还没有领取权益" :show-icon="false">
<EmptyBox v-if="!minePending && !mineList.length" text="还没有领取权益" :show-icon="false">
<view class="mt-6 flex justify-center">
<view
class="h-9 px-[18px] rounded-full flex items-center border-[0.5px] border-brand text-cap font-semibold text-brand-deep"

View File

@@ -18,6 +18,8 @@ import { goCatalog, previewImages } from "@/utils/nav";
const home = ref({});
const featured = ref([]);
/** 首屏数据是否已回来(骨架屏开关;请求失败也置真,避免骨架常驻) */
const loaded = ref(false);
const brandName = computed(() => home.value?.brand_name || "");
/** 品牌名末两字走品牌色(「发现龙里」→ 发现 + 龙里) */
@@ -50,6 +52,7 @@ const load = async () => {
featured.value = [];
} finally {
loading = false;
loaded.value = true;
}
};
@@ -76,6 +79,59 @@ const onMapTap = () => {
<template>
<view class="relative h-screen w-full flex flex-col skin-home">
<!-- 首屏骨架屏数据未到时按真实结构占位品牌区 / 主视觉 / 玩转龙里 / 导览 / 精选 -->
<template v-if="!loaded">
<view class="relative z-[60] shrink-0">
<view class="px-4 pb-[10px]">
<AppStatusBar />
<view class="mt-[6px] h-[14px] w-[84px] rounded-[7px] animate-pulse bg-skel" />
<view class="mt-[9px] h-[32px] w-[186px] rounded-[10px] animate-pulse bg-skel" />
<view class="mt-2 h-[18px] w-[234px] rounded-[6px] animate-pulse bg-skel" />
</view>
</view>
<scroll-view class="flex-1 min-h-0" scroll-y :show-scrollbar="false">
<view class="px-4">
<view class="h-[180px] mt-[2px] rounded-hero animate-pulse bg-skel" />
<view class="flex items-center h-[24px] mt-5 mb-[10px] mx-[2px]">
<view class="h-[17px] w-[68px] rounded-[6px] animate-pulse bg-skel" />
</view>
<view class="flex gap-[10px] h-[226px] mt-[2px]">
<view class="flex-[1.06] min-w-0 rounded-tile animate-pulse bg-skel" />
<view class="flex-1 min-w-0 flex flex-col gap-[10px]">
<view class="h-[108px] rounded-tile animate-pulse bg-skel" />
<view class="h-[108px] rounded-tile animate-pulse bg-skel" />
</view>
</view>
<view class="flex items-center h-[24px] mt-5 mb-[10px] mx-[2px]">
<view class="h-[17px] w-[68px] rounded-[6px] animate-pulse bg-skel" />
</view>
<view class="h-[112px] mt-[2px] rounded-tile animate-pulse bg-skel" />
<view class="flex items-center justify-between h-[24px] mt-5 mb-[10px] mx-[2px]">
<view class="h-[17px] w-[68px] rounded-[6px] animate-pulse bg-skel" />
<view class="h-[12px] w-[36px] rounded-[6px] animate-pulse bg-skel" />
</view>
<view v-for="i in 3" :key="i"
class="flex gap-[11px] bg-white rounded-card p-[10px] shadow-elev1 border-[0.5px] border-line"
:class="i > 1 ? 'mt-[10px]' : ''">
<view class="shrink-0 w-[92px] h-[92px] rounded-[12px] animate-pulse bg-skel" />
<view class="min-w-0 flex-1">
<view class="h-[13px] w-[72%] rounded-[6px] animate-pulse bg-skel" />
<view class="mt-[9px] h-[10px] w-[94%] rounded-[5px] animate-pulse bg-skel" />
<view class="mt-[6px] h-[10px] w-[58%] rounded-[5px] animate-pulse bg-skel" />
</view>
</view>
</view>
<view class="h-[96px]" />
</scroll-view>
</template>
<template v-else>
<view class="relative z-[60] shrink-0">
<view class="px-4 pb-[10px]">
<AppStatusBar />
@@ -175,5 +231,6 @@ const onMapTap = () => {
<!-- 底部留白 = TabBar 78px + 18px 间隔避免最后一条内容贴住 TabBar -->
<view class="h-[96px]" />
</scroll-view>
</template>
</view>
</template>