feat: 添加了商品详情页的骨架屏请求效果
This commit is contained in:
64
src/pages/goods/components/GoodsDetailSkeleton/index.vue
Normal file
64
src/pages/goods/components/GoodsDetailSkeleton/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<view class="flex min-h-0 flex-1 flex-col overflow-hidden bg-[#f5f5f5]">
|
||||
<view class="skeleton-block relative h-[300px] w-full shrink-0 overflow-hidden bg-[#e7ebef]" />
|
||||
|
||||
<view class="relative z-10 -mt-[30px] min-h-0 flex-1 rounded-t-[28px] bg-white px-[16px] pb-[24px] pt-[24px]">
|
||||
<view class="skeleton-block relative h-[22px] w-[72%] overflow-hidden rounded-[6px] bg-[#e9edf1]" />
|
||||
<view class="mt-[12px] flex gap-[8px]">
|
||||
<view class="skeleton-block relative h-[20px] w-[64px] overflow-hidden rounded-[4px] bg-[#edf0f3]" />
|
||||
<view class="skeleton-block relative h-[20px] w-[76px] overflow-hidden rounded-[4px] bg-[#edf0f3]" />
|
||||
<view class="skeleton-block relative h-[20px] w-[56px] overflow-hidden rounded-[4px] bg-[#edf0f3]" />
|
||||
</view>
|
||||
<view class="mt-[18px] flex items-end">
|
||||
<view class="skeleton-block relative h-[28px] w-[110px] overflow-hidden rounded-[6px] bg-[#e9edf1]" />
|
||||
<view class="skeleton-block relative ml-[12px] h-[16px] w-[70px] overflow-hidden rounded-[4px] bg-[#f0f2f4]" />
|
||||
</view>
|
||||
|
||||
<view class="mt-[24px] rounded-[12px] bg-[#f8f9fa] p-[14px]">
|
||||
<view class="flex items-center">
|
||||
<view class="skeleton-block relative h-[24px] w-[24px] overflow-hidden rounded-full bg-[#e5e9ed]" />
|
||||
<view class="ml-[10px] min-w-0 flex-1">
|
||||
<view class="skeleton-block relative h-[16px] w-[58%] overflow-hidden rounded-[4px] bg-[#e8ecef]" />
|
||||
<view class="skeleton-block relative mt-[8px] h-[12px] w-[86%] overflow-hidden rounded-[4px] bg-[#eef1f3]" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-[24px]">
|
||||
<view class="skeleton-block relative h-[20px] w-[88px] overflow-hidden rounded-[5px] bg-[#e8ecef]" />
|
||||
<view class="skeleton-block relative mt-[14px] h-[14px] w-full overflow-hidden rounded-[4px] bg-[#eef1f3]" />
|
||||
<view class="skeleton-block relative mt-[10px] h-[14px] w-[92%] overflow-hidden rounded-[4px] bg-[#eef1f3]" />
|
||||
<view class="skeleton-block relative mt-[10px] h-[14px] w-[76%] overflow-hidden rounded-[4px] bg-[#eef1f3]" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex shrink-0 items-center bg-white px-[12px] pb-[24px] pt-[12px]">
|
||||
<view class="skeleton-block relative h-[28px] w-[92px] overflow-hidden rounded-[6px] bg-[#e9edf1]" />
|
||||
<view class="skeleton-block relative ml-auto h-[48px] w-[120px] overflow-hidden rounded-[10px] bg-[#e4e8ec]" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.skeleton-block {
|
||||
&::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
content: "";
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.7) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
transform: translateX(-100%);
|
||||
animation: skeleton-shimmer 1.4s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,23 @@
|
||||
<template>
|
||||
<view class="goods-container bg-[#f5f5f5]">
|
||||
<TopNavBar :title="navOpacity < 0.5 ? '' : '商品详情'" :background="`rgba(217, 238, 255, ${navOpacity})`"
|
||||
:titleColor="navOpacity < 0.5 ? '#ffffff' : '#000000'"
|
||||
:backIconColor="navOpacity < 0.5 ? '#ffffff' : '#000000'" />
|
||||
<TopNavBar :title="solidNavBar || navOpacity >= 0.5 ? '商品详情' : ''"
|
||||
:background="solidNavBar ? '#ffffff' : `rgba(217, 238, 255, ${navOpacity})`"
|
||||
:titleColor="solidNavBar || navOpacity >= 0.5 ? '#000000' : '#ffffff'"
|
||||
:backIconColor="solidNavBar || navOpacity >= 0.5 ? '#000000' : '#ffffff'" />
|
||||
|
||||
<GoodsDetailSkeleton v-if="loadStatus === 'loading'" />
|
||||
|
||||
<view v-else-if="loadStatus === 'error' || loadStatus === 'empty'"
|
||||
class="box-border flex min-h-0 flex-1 flex-col items-center justify-center px-[24px] pb-[80px] pt-[96px]">
|
||||
<CustomEmpty :statusText="loadStatus === 'error' ? '商品加载失败' : '暂无商品数据'" />
|
||||
<view v-if="loadStatus === 'error'"
|
||||
class="mt-[20px] flex h-[40px] items-center justify-center rounded-full bg-[#ff3d60] px-[28px]"
|
||||
@click="reloadGoods">
|
||||
<text class="text-[14px] font-[500] text-white">重新加载</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<!-- 滚动区域 -->
|
||||
<scroll-view class="content-wrapper" scroll-y @scroll="handleScroll">
|
||||
<ImageSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
||||
@@ -48,11 +62,12 @@
|
||||
<!-- 日历组件 -->
|
||||
<Calender :visible="calendarVisible" mode="range" :range-require-price="true" :price-data="priceData"
|
||||
@close="handleCalendarClose" @range-select="handleDateSelect" />
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { goodsDetail, commodityDailyPriceList } from "@/request/api/GoodsApi";
|
||||
import { DateUtils } from "@/utils";
|
||||
@@ -65,12 +80,17 @@ import DateSelector from "./components/DateSelector/index.vue";
|
||||
import GoodDetail from "@/components/GoodDetail/index.vue";
|
||||
import GoodFacility from "./components/GoodFacility/index.vue";
|
||||
import GoodPackage from "./components/GoodPackage/index.vue";
|
||||
import GoodsDetailSkeleton from "./components/GoodsDetailSkeleton/index.vue";
|
||||
import CustomEmpty from "@/components/CustomEmpty/index.vue";
|
||||
import { useSelectedDateStore } from "@/store";
|
||||
|
||||
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
||||
const navOpacity = ref(0);
|
||||
const calendarVisible = ref(false);
|
||||
const goodsData = ref({});
|
||||
const loadStatus = ref("loading");
|
||||
const currentCommodityId = ref("");
|
||||
const solidNavBar = computed(() => loadStatus.value !== "success");
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = (e) => {
|
||||
@@ -87,15 +107,38 @@ const selectedDate = ref({
|
||||
totalDays: 1,
|
||||
});
|
||||
|
||||
const priceData = ref([])
|
||||
const priceData = ref([]);
|
||||
|
||||
// 计算的总价格
|
||||
const calculatedTotalPrice = ref(0);
|
||||
|
||||
// 获取商品详情数据
|
||||
const goodsInfo = async (params) => {
|
||||
loadStatus.value = "loading";
|
||||
goodsData.value = {};
|
||||
calculatedTotalPrice.value = 0;
|
||||
priceData.value = [];
|
||||
|
||||
try {
|
||||
const res = await goodsDetail(params);
|
||||
|
||||
if (res?.code !== 0) {
|
||||
console.error("查询商品详情失败:", res);
|
||||
loadStatus.value = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!res.data ||
|
||||
typeof res.data !== "object" ||
|
||||
Array.isArray(res.data) ||
|
||||
Object.keys(res.data).length === 0 ||
|
||||
!res.data.commodityId
|
||||
) {
|
||||
loadStatus.value = "empty";
|
||||
return;
|
||||
}
|
||||
|
||||
goodsData.value = res.data;
|
||||
|
||||
// 初始化计算价格
|
||||
@@ -109,17 +152,22 @@ const goodsInfo = async (params) => {
|
||||
});
|
||||
}
|
||||
|
||||
loadStatus.value = "success";
|
||||
|
||||
if (goodsData.value.commodityStatus !== "1") {
|
||||
uni.showModal({
|
||||
title: "温馨提示",
|
||||
content: "您选的商品暂不可预订,请重新选择。",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("查询商品详情失败:", error);
|
||||
loadStatus.value = "error";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,12 +187,19 @@ const getGoodsDailyPrice = async (params) => {
|
||||
|
||||
const selectedDateStore = useSelectedDateStore();
|
||||
onLoad(({ commodityId = "1950766939442774018" }) => {
|
||||
currentCommodityId.value = commodityId;
|
||||
|
||||
// 从store中获取选中的日期
|
||||
selectedDate.value = selectedDateStore.selectedDate;
|
||||
|
||||
goodsInfo({ commodityId });
|
||||
});
|
||||
|
||||
const reloadGoods = () => {
|
||||
if (!currentCommodityId.value) return;
|
||||
goodsInfo({ commodityId: currentCommodityId.value });
|
||||
};
|
||||
|
||||
// 显示日历弹窗
|
||||
const showCalendar = () => (calendarVisible.value = true);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user