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,58 +1,73 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="goods-container bg-[#f5f5f5]">
|
<view class="goods-container bg-[#f5f5f5]">
|
||||||
<TopNavBar :title="navOpacity < 0.5 ? '' : '商品详情'" :background="`rgba(217, 238, 255, ${navOpacity})`"
|
<TopNavBar :title="solidNavBar || navOpacity >= 0.5 ? '商品详情' : ''"
|
||||||
:titleColor="navOpacity < 0.5 ? '#ffffff' : '#000000'"
|
:background="solidNavBar ? '#ffffff' : `rgba(217, 238, 255, ${navOpacity})`"
|
||||||
:backIconColor="navOpacity < 0.5 ? '#ffffff' : '#000000'" />
|
:titleColor="solidNavBar || navOpacity >= 0.5 ? '#000000' : '#ffffff'"
|
||||||
|
:backIconColor="solidNavBar || navOpacity >= 0.5 ? '#000000' : '#ffffff'" />
|
||||||
|
|
||||||
<!-- 滚动区域 -->
|
<GoodsDetailSkeleton v-if="loadStatus === 'loading'" />
|
||||||
<scroll-view class="content-wrapper" scroll-y @scroll="handleScroll">
|
|
||||||
<ImageSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
|
||||||
|
|
||||||
<view class="goods-content">
|
<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]">
|
||||||
<GoodInfo :goodsData="goodsData" />
|
<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]"
|
||||||
<LocationCard :orderData="goodsData" />
|
@click="reloadGoods">
|
||||||
|
<text class="text-[14px] font-[500] text-white">重新加载</text>
|
||||||
<!-- 日期选择区域 -->
|
|
||||||
<DateSelector v-if="goodsData.orderType == 0" @showCalendar="showCalendar" :checkInDate="selectedDate.startDate"
|
|
||||||
:checkOutDate="selectedDate.endDate" :checkInDay="''" :checkOutDay="''" :nights="selectedDate.totalDays" />
|
|
||||||
|
|
||||||
<!-- 商品套餐组件 -->
|
|
||||||
<GoodPackage
|
|
||||||
v-if="goodsData.orderType != 0 && goodsData.commodityPackageConfig && goodsData.commodityPackageConfig.length > 0"
|
|
||||||
:goodsData="goodsData" />
|
|
||||||
|
|
||||||
<!-- 商品设施组件 -->
|
|
||||||
<GoodFacility v-if="goodsData.commodityEquipment && goodsData.commodityEquipment.length > 0"
|
|
||||||
:goodsData="goodsData" />
|
|
||||||
|
|
||||||
<!-- 商品详情组件 -->
|
|
||||||
<GoodDetail :goodsData="goodsData" />
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 立即抢购 -->
|
|
||||||
<view class="footer border-top">
|
|
||||||
<view class="amt text-[20px] font-bold text-[#ff3d60] line-height-28 flex items-center mr-[8px]">
|
|
||||||
{{ calculatedTotalPrice }}
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="btn rounded-[10px] flex items-center ml-auto pl-[8px]" @click="navigateToPay(goodsData)">
|
|
||||||
<image class="icon" src="https://oss.nianxx.cn/mp/static/version_101/common/btn.png" />
|
|
||||||
<text class="text-[16px] font-[500] text-white">立即预定</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日历组件 -->
|
<template v-else>
|
||||||
<Calender :visible="calendarVisible" mode="range" :range-require-price="true" :price-data="priceData"
|
<!-- 滚动区域 -->
|
||||||
@close="handleCalendarClose" @range-select="handleDateSelect" />
|
<scroll-view class="content-wrapper" scroll-y @scroll="handleScroll">
|
||||||
|
<ImageSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
||||||
|
|
||||||
|
<view class="goods-content">
|
||||||
|
<!-- 商品信息组件 -->
|
||||||
|
<GoodInfo :goodsData="goodsData" />
|
||||||
|
|
||||||
|
<!-- 地址区域 -->
|
||||||
|
<LocationCard :orderData="goodsData" />
|
||||||
|
|
||||||
|
<!-- 日期选择区域 -->
|
||||||
|
<DateSelector v-if="goodsData.orderType == 0" @showCalendar="showCalendar" :checkInDate="selectedDate.startDate"
|
||||||
|
:checkOutDate="selectedDate.endDate" :checkInDay="''" :checkOutDay="''" :nights="selectedDate.totalDays" />
|
||||||
|
|
||||||
|
<!-- 商品套餐组件 -->
|
||||||
|
<GoodPackage
|
||||||
|
v-if="goodsData.orderType != 0 && goodsData.commodityPackageConfig && goodsData.commodityPackageConfig.length > 0"
|
||||||
|
:goodsData="goodsData" />
|
||||||
|
|
||||||
|
<!-- 商品设施组件 -->
|
||||||
|
<GoodFacility v-if="goodsData.commodityEquipment && goodsData.commodityEquipment.length > 0"
|
||||||
|
:goodsData="goodsData" />
|
||||||
|
|
||||||
|
<!-- 商品详情组件 -->
|
||||||
|
<GoodDetail :goodsData="goodsData" />
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 立即抢购 -->
|
||||||
|
<view class="footer border-top">
|
||||||
|
<view class="amt text-[20px] font-bold text-[#ff3d60] line-height-28 flex items-center mr-[8px]">
|
||||||
|
{{ calculatedTotalPrice }}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="btn rounded-[10px] flex items-center ml-auto pl-[8px]" @click="navigateToPay(goodsData)">
|
||||||
|
<image class="icon" src="https://oss.nianxx.cn/mp/static/version_101/common/btn.png" />
|
||||||
|
<text class="text-[16px] font-[500] text-white">立即预定</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 日历组件 -->
|
||||||
|
<Calender :visible="calendarVisible" mode="range" :range-require-price="true" :price-data="priceData"
|
||||||
|
@close="handleCalendarClose" @range-select="handleDateSelect" />
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import { goodsDetail, commodityDailyPriceList } from "@/request/api/GoodsApi";
|
import { goodsDetail, commodityDailyPriceList } from "@/request/api/GoodsApi";
|
||||||
import { DateUtils } from "@/utils";
|
import { DateUtils } from "@/utils";
|
||||||
@@ -65,12 +80,17 @@ import DateSelector from "./components/DateSelector/index.vue";
|
|||||||
import GoodDetail from "@/components/GoodDetail/index.vue";
|
import GoodDetail from "@/components/GoodDetail/index.vue";
|
||||||
import GoodFacility from "./components/GoodFacility/index.vue";
|
import GoodFacility from "./components/GoodFacility/index.vue";
|
||||||
import GoodPackage from "./components/GoodPackage/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";
|
import { useSelectedDateStore } from "@/store";
|
||||||
|
|
||||||
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
||||||
const navOpacity = ref(0);
|
const navOpacity = ref(0);
|
||||||
const calendarVisible = ref(false);
|
const calendarVisible = ref(false);
|
||||||
const goodsData = ref({});
|
const goodsData = ref({});
|
||||||
|
const loadStatus = ref("loading");
|
||||||
|
const currentCommodityId = ref("");
|
||||||
|
const solidNavBar = computed(() => loadStatus.value !== "success");
|
||||||
|
|
||||||
// 处理滚动事件
|
// 处理滚动事件
|
||||||
const handleScroll = (e) => {
|
const handleScroll = (e) => {
|
||||||
@@ -87,39 +107,67 @@ const selectedDate = ref({
|
|||||||
totalDays: 1,
|
totalDays: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const priceData = ref([])
|
const priceData = ref([]);
|
||||||
|
|
||||||
// 计算的总价格
|
// 计算的总价格
|
||||||
const calculatedTotalPrice = ref(0);
|
const calculatedTotalPrice = ref(0);
|
||||||
|
|
||||||
// 获取商品详情数据
|
// 获取商品详情数据
|
||||||
const goodsInfo = async (params) => {
|
const goodsInfo = async (params) => {
|
||||||
const res = await goodsDetail(params);
|
loadStatus.value = "loading";
|
||||||
|
goodsData.value = {};
|
||||||
|
calculatedTotalPrice.value = 0;
|
||||||
|
priceData.value = [];
|
||||||
|
|
||||||
goodsData.value = res.data;
|
try {
|
||||||
|
const res = await goodsDetail(params);
|
||||||
|
|
||||||
// 初始化计算价格
|
if (res?.code !== 0) {
|
||||||
calculatedTotalPrice.value = goodsData.value.specificationPrice || 0;
|
console.error("查询商品详情失败:", res);
|
||||||
|
loadStatus.value = "error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是酒店类型订单再获取获取商品日价格及库存
|
if (
|
||||||
if (goodsData.value.orderType == 0) {
|
!res.data ||
|
||||||
configGoodsData();
|
typeof res.data !== "object" ||
|
||||||
getGoodsDailyPrice({
|
Array.isArray(res.data) ||
|
||||||
commodityId: goodsData.value.commodityId,
|
Object.keys(res.data).length === 0 ||
|
||||||
});
|
!res.data.commodityId
|
||||||
}
|
) {
|
||||||
|
loadStatus.value = "empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (goodsData.value.commodityStatus !== "1") {
|
goodsData.value = res.data;
|
||||||
uni.showModal({
|
|
||||||
title: "温馨提示",
|
// 初始化计算价格
|
||||||
content: "您选的商品暂不可预订,请重新选择。",
|
calculatedTotalPrice.value = goodsData.value.specificationPrice || 0;
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
// 判断是酒店类型订单再获取获取商品日价格及库存
|
||||||
uni.navigateBack({ delta: 1 });
|
if (goodsData.value.orderType == 0) {
|
||||||
}
|
configGoodsData();
|
||||||
},
|
getGoodsDailyPrice({
|
||||||
});
|
commodityId: goodsData.value.commodityId,
|
||||||
return;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStatus.value = "success";
|
||||||
|
|
||||||
|
if (goodsData.value.commodityStatus !== "1") {
|
||||||
|
uni.showModal({
|
||||||
|
title: "温馨提示",
|
||||||
|
content: "您选的商品暂不可预订,请重新选择。",
|
||||||
|
success: (modalRes) => {
|
||||||
|
if (modalRes.confirm) {
|
||||||
|
uni.navigateBack({ delta: 1 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("查询商品详情失败:", error);
|
||||||
|
loadStatus.value = "error";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,12 +187,19 @@ const getGoodsDailyPrice = async (params) => {
|
|||||||
|
|
||||||
const selectedDateStore = useSelectedDateStore();
|
const selectedDateStore = useSelectedDateStore();
|
||||||
onLoad(({ commodityId = "1950766939442774018" }) => {
|
onLoad(({ commodityId = "1950766939442774018" }) => {
|
||||||
|
currentCommodityId.value = commodityId;
|
||||||
|
|
||||||
// 从store中获取选中的日期
|
// 从store中获取选中的日期
|
||||||
selectedDate.value = selectedDateStore.selectedDate;
|
selectedDate.value = selectedDateStore.selectedDate;
|
||||||
|
|
||||||
goodsInfo({ commodityId });
|
goodsInfo({ commodityId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const reloadGoods = () => {
|
||||||
|
if (!currentCommodityId.value) return;
|
||||||
|
goodsInfo({ commodityId: currentCommodityId.value });
|
||||||
|
};
|
||||||
|
|
||||||
// 显示日历弹窗
|
// 显示日历弹窗
|
||||||
const showCalendar = () => (calendarVisible.value = true);
|
const showCalendar = () => (calendarVisible.value = true);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user