Browse Source

feat: 优化细节新增骨架屏

叶静 3 weeks ago
parent
commit
4c53385e9f
3 changed files with 501 additions and 263 deletions
  1. 80 31
      src/pages/myOrders/myOrders.vue
  2. 207 115
      src/pages/myOrders/orderDetail.vue
  3. 214 117
      src/pages/productDetail/productDetail.vue

+ 80 - 31
src/pages/myOrders/myOrders.vue

@@ -29,6 +29,7 @@ const userInfo = computed(() => {
 const paging = ref(null)
 // 类似mixins,如果是页面滚动务必要写这一行,并传入当前ref绑定的paging,注意此处是paging,而非paging.value
 useZPaging(paging)
+const isPageLoading = ref(true) // 页面加载状态
 const tab = ref<number>(0)
 const tabList = ref<any>([
   {
@@ -70,6 +71,11 @@ const dataList = ref<any[]>([])
 async function queryList(pageNo: number, pageSize: number) {
   // 此处请求仅为演示,请替换为自己项目中的请求
   try {
+    // 第一页加载时显示骨架屏
+    if (pageNo === 1) {
+      isPageLoading.value = true
+    }
+
     const res = await orderList({
       page: pageNo,
       size: pageSize,
@@ -80,6 +86,12 @@ async function queryList(pageNo: number, pageSize: number) {
   catch {
     paging.value.complete(false)
   }
+  finally {
+    // 第一页加载完成后隐藏骨架屏
+    if (pageNo === 1) {
+      isPageLoading.value = false
+    }
+  }
 }
 onLoad((options) => {
   getOrderStatus()
@@ -105,45 +117,82 @@ onLoad((options) => {
         <wd-tab v-for="tabItem in tabList" :key="tabItem.value" :title="t(tabItem.label)" :name="tabItem.value" />
       </wd-tabs>
     </template>
-    <view class="pt-24rpx">
-      <wd-card v-for="item in dataList" :key="item.orderId" type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage('/pages/myOrders/orderDetail', { id: item.id })">
-        <template #title>
-          <view class="flex items-center justify-between">
-            <view class="text-28rpx text-#000">
-              {{ $t('myOrders.order.id') }}:{{ item.orderId }}
-            </view>
-            <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === item.status)?.name" />
-          </view>
-        </template>
-        <view class="flex items-center justify-center gap-24rpx">
-          <view class="h-140rpx w-140rpx shrink-0 text-center">
-            <image
-              :src="item?.orderInfoVO?.[0]?.image"
-              class="h-full w-full"
-              mode="aspectFit"
+
+    <!-- 页面加载时显示骨架屏 -->
+    <template v-if="isPageLoading">
+      <view class="pt-24rpx">
+        <!-- 订单卡片骨架屏 -->
+        <wd-card v-for="i in 3" :key="i" type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!">
+          <template #title>
+            <!-- 订单头部:订单号 + 状态 -->
+            <wd-skeleton
+              :row-col="[
+                [{ width: '250rpx', height: '28rpx' }, { width: '60rpx', height: '26rpx', marginLeft: 'auto' }],
+              ]"
+              animation="gradient"
             />
-          </view>
-          <view class="flex-1">
-            <view class="line-clamp-2 h-80rpx text-28rpx text-#000">
-              {{ item?.orderInfoVO?.[0]?.productName }}
+          </template>
+          <!-- 商品区域:图片 + 信息 -->
+          <wd-skeleton
+            :row-col="[
+              [
+                { width: '140rpx', height: '140rpx', type: 'rect' },
+                [
+                  { width: '100%', height: '40rpx' }, // 商品标题第一行
+                  { width: '80%', height: '40rpx', marginTop: '8rpx' }, // 商品标题第二行
+                  { width: '120rpx', height: '24rpx', marginTop: '4rpx' }, // 颜色规格
+                  [{ width: '100rpx', height: '24rpx' }, { width: '80rpx', height: '24rpx', marginLeft: 'auto' }], // 价格和数量
+                ],
+              ],
+            ]"
+            animation="gradient"
+          />
+        </wd-card>
+      </view>
+    </template>
+
+    <!-- 实际内容 -->
+    <template v-else>
+      <view class="pt-24rpx">
+        <wd-card v-for="item in dataList" :key="item.orderId" type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage('/pages/myOrders/orderDetail', { id: item.id })">
+          <template #title>
+            <view class="flex items-center justify-between">
+              <view class="text-28rpx text-#000">
+                {{ t('myOrders.order.id') }}:{{ item.orderId }}
+              </view>
+              <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === item.status)?.name" />
+            </view>
+          </template>
+          <view class="flex items-center justify-center gap-24rpx">
+            <view class="h-140rpx w-140rpx shrink-0 text-center">
+              <image
+                :src="item?.orderInfoVO?.[0]?.image"
+                class="h-full w-full"
+                mode="aspectFit"
+              />
             </view>
-            <view>
-              <view class="py-4rpx text-24rpx text-#3A444C">
-                {{ $t('myOrders.order.color') }}: {{ item?.orderInfoVO?.[0]?.sku }}
+            <view class="flex-1">
+              <view class="line-clamp-2 h-80rpx text-28rpx text-#000">
+                {{ item?.orderInfoVO?.[0]?.productName }}
               </view>
-              <view class="flex items-center justify-between text-24rpx text-#3A444C">
-                <view>
-                  ৳ {{ formatNumber(item?.orderInfoVO?.[0]?.price) }}
+              <view>
+                <view class="py-4rpx text-24rpx text-#3A444C">
+                  {{ t('myOrders.order.color') }}: {{ item?.orderInfoVO?.[0]?.sku }}
                 </view>
-                <view>
-                  {{ $t('myOrders.order.quantity') }}:{{ item?.orderInfoVO?.[0]?.payNum }}
+                <view class="flex items-center justify-between text-24rpx text-#3A444C">
+                  <view>
+                    ৳ {{ formatNumber(item?.orderInfoVO?.[0]?.price) }}
+                  </view>
+                  <view>
+                    {{ t('myOrders.order.quantity') }}:{{ item?.orderInfoVO?.[0]?.payNum }}
+                  </view>
                 </view>
               </view>
             </view>
           </view>
-        </view>
-      </wd-card>
-    </view>
+        </wd-card>
+      </view>
+    </template>
   </z-paging>
 </template>
 

+ 207 - 115
src/pages/myOrders/orderDetail.vue

@@ -29,6 +29,7 @@ const paging = ref(null)
 const id = ref<any>()
 const orderNo = ref<string>()
 const isPayOrder = ref<boolean>(false)
+const isPageLoading = ref(true) // 页面加载状态
 const detail = ref<any>({})
 const orderStatusEnumData = ref<any>([])
 const openRedEnvelopeRate = ref<any>()
@@ -104,10 +105,8 @@ function startCountdown() {
 }
 
 async function getDetail() {
-  uni.showLoading({
-    title: t('orderDetail.loading'),
-  })
   try {
+    isPageLoading.value = true
     // 根据参数类型构建请求参数
     const params = orderNo.value ? { orderNo: orderNo.value } : { id: id.value }
     const res = await orderDetail(params)
@@ -129,7 +128,7 @@ async function getDetail() {
     }
   }
   finally {
-    uni.hideLoading()
+    isPageLoading.value = false
   }
 }
 const pinkList = ref<any>([])
@@ -310,141 +309,233 @@ onUnmounted(() => {
 
 <template>
   <z-paging ref="paging" refresher-only @refresh="getDetail">
-    <view class="pt-20rpx">
-      <!-- 状态显示 -->
-      <template v-if="detail.status !== 4 && detail?.status !== 2">
-        <!-- 已中奖 -->
-        <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
-          {{ t('orderDetail.congrats') }}
-          <br>
-          {{ t('orderDetail.receiveReward') }}
-          <text class="text-[var(--wot-color-theme)]">
-            ৳{{ detail.brokerage }}
-          </text>
-        </view>
-        <!-- 未中奖 -->
-        <view v-else-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 0" class="mb-20rpx bg-#E61B28/80 py-20rpx text-center text-28rpx text-white">
-          {{ t('orderDetail.sorry') }}
-          <br>
-          {{ t('orderDetail.receiveReward') }}
-          <text class="text-#66C59B">
-            ৳{{ detail.brokerage }}
-          </text>
+    <!-- 页面加载时显示骨架屏 -->
+    <template v-if="isPageLoading">
+      <view class="pt-20rpx">
+        <!-- 状态卡片骨架屏(模拟状态显示区域) -->
+        <view class="mb-20rpx bg-white py-20rpx text-center">
+          <wd-skeleton
+            :row-col="[{ width: '180rpx', height: '28rpx', marginLeft: 'auto', marginRight: 'auto' }]"
+            animation="gradient"
+          />
         </view>
-        <!-- 未开奖||未支付 -->
-        <view v-else-if="detail?.storePink?.status === 1 || detail?.status === 1" class="mb-20rpx bg-#fff py-20rpx text-center text-28rpx text-white">
-          <text v-if="detail?.storePink?.status === 1 && detail?.status !== 1" class="text-[var(--wot-color-theme)]">
-            {{ t('orderDetail.waiting') }}
-          </text>
-          <text v-else class="text-[var(--wot-color-theme)]">
-            {{ t('orderDetail.paymentCountdown') }} {{ countdown }}
-          </text>
+
+        <!-- 地址信息骨架屏(模拟地址选择区域) -->
+        <view class="mb-20rpx bg-white px-24rpx py-20rpx">
+          <wd-skeleton
+            :row-col="[
+              { width: '150rpx', height: '28rpx' }, // 地址标题或添加地址
+              { width: '200rpx', height: '24rpx', marginTop: '12rpx' }, // 收货人信息
+              { width: '100%', height: '22rpx', marginTop: '8rpx' }, // 详细地址
+            ]"
+            animation="gradient"
+          />
         </view>
-        <!-- 拼团头像 -->
-        <view v-if="pinkList && pinkList.length" class="mb-20rpx bg-white px-20rpx py-20rpx text-center">
-          <image v-for="i in pinkList" :key="i" class="mx-4rpx mb-8rpx h-80rpx w-80rpx rounded-full" :src="i.avatar" />
+
+        <!-- 商品信息骨架屏(使用实际的 wd-card 结构) -->
+        <wd-card type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!">
+          <template #title>
+            <!-- 订单头部:订单号 + 状态 -->
+            <wd-skeleton
+              :row-col="[
+                [{ width: '250rpx', height: '28rpx' }, { width: '60rpx', height: '26rpx', marginLeft: 'auto' }],
+              ]"
+              animation="gradient"
+            />
+          </template>
+          <!-- 商品区域:图片 + 信息 -->
+          <wd-skeleton
+            :row-col="[
+              [
+                { width: '140rpx', height: '140rpx', type: 'rect' },
+                [
+                  { width: '100%', height: '40rpx' }, // 商品标题第一行
+                  { width: '80%', height: '40rpx', marginTop: '8rpx' }, // 商品标题第二行
+                  { width: '120rpx', height: '24rpx', marginTop: '4rpx' }, // 颜色规格
+                  [{ width: '100rpx', height: '24rpx' }, { width: '80rpx', height: '24rpx', marginLeft: 'auto' }], // 价格和数量
+                ],
+              ],
+            ]"
+            animation="gradient"
+          />
+        </wd-card>
+
+        <!-- 订单信息骨架屏 -->
+        <view class="bg-white px-24rpx">
+          <!-- 订单摘要 -->
+          <view class="border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
+            <wd-skeleton
+              :row-col="[
+                { width: '120rpx', height: '28rpx' }, // 摘要标题
+                [{ width: '80rpx', height: '24rpx' }, { width: '100rpx', height: '24rpx', marginLeft: 'auto' }], // SubTotal 行
+              ]"
+              animation="gradient"
+            />
+          </view>
+
+          <!-- 支付方式 -->
+          <view class="border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
+            <wd-skeleton
+              :row-col="[
+                [{ width: '100rpx', height: '28rpx' }, { width: '140rpx', height: '24rpx', marginLeft: 'auto' }],
+              ]"
+              animation="gradient"
+            />
+          </view>
+
+          <!-- 订单状态记录 -->
+          <view class="py-24rpx">
+            <wd-skeleton
+              :row-col="[
+                [{ width: '90rpx', height: '24rpx' }, { width: '150rpx', height: '24rpx', marginLeft: 'auto' }],
+                [{ width: '80rpx', height: '24rpx' }, { width: '150rpx', height: '24rpx', marginLeft: 'auto' }],
+              ]"
+              animation="gradient"
+            />
+          </view>
         </view>
-      </template>
-      <!-- 地址 -->
-      <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-white px-24rpx py-20rpx">
-        <!-- 无地址 -->
-        <template v-if="!detail.orderAddressVO">
-          <view class="flex items-center justify-between" @click="selectAddress">
-            <view class="text-28rpx text-[var(--wot-color-theme)]">
-              {{ t('orderDetail.address.add') }}
-            </view>
-            <wd-icon name="arrow-right" color="#7D7D7D" size="28rpx" />
+      </view>
+    </template>
+
+    <!-- 实际内容 -->
+    <template v-else>
+      <view class="pt-20rpx">
+        <!-- 状态显示 -->
+        <template v-if="detail.status !== 4 && detail?.status !== 2">
+          <!-- 已中奖 -->
+          <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
+            {{ t('orderDetail.congrats') }}
+            <br>
+            {{ t('orderDetail.receiveReward') }}
+            <text class="text-[var(--wot-color-theme)]">
+              ৳{{ detail.brokerage }}
+            </text>
           </view>
-        </template>
-        <!-- 有地址 -->
-        <template v-else>
-          <view v-if="detail.deliveryCode" class="mb-18rpx flex justify-between border-b-1 border-b-#e8e8e8 border-b-solid pb-18rpx text-24rpx">
-            <!-- 物流信息 -->
-            <view>{{ detail.deliveryName }}</view>
-            <view> DHL:{{ detail.deliveryCode || '-' }}</view>
+          <!-- 未中奖 -->
+          <view v-else-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 0" class="mb-20rpx bg-#E61B28/80 py-20rpx text-center text-28rpx text-white">
+            {{ t('orderDetail.sorry') }}
+            <br>
+            {{ t('orderDetail.receiveReward') }}
+            <text class="text-#66C59B">
+              ৳{{ detail.brokerage }}
+            </text>
           </view>
-          <view class="mb-20rpx text-24rpx">
-            <text class="mr-20rpx">
-              {{ detail?.orderAddressVO?.realName }}
+          <!-- 未开奖||未支付 -->
+          <view v-else-if="detail?.storePink?.status === 1 || detail?.status === 1" class="mb-20rpx bg-#fff py-20rpx text-center text-28rpx text-white">
+            <text v-if="detail?.storePink?.status === 1 && detail?.status !== 1" class="text-[var(--wot-color-theme)]">
+              {{ t('orderDetail.waiting') }}
+            </text>
+            <text v-else class="text-[var(--wot-color-theme)]">
+              {{ t('orderDetail.paymentCountdown') }} {{ countdown }}
             </text>
-            <text>{{ detail?.orderAddressVO?.phone }}</text>
           </view>
-          <view class="text-22rpx text-#3A444C">
-            {{ detail?.orderAddressVO?.province }} {{ detail?.orderAddressVO?.city }} {{ detail?.orderAddressVO?.district }} {{ detail?.orderAddressVO?.detail }} {{ detail?.orderAddressVO?.postCode }}
+          <!-- 拼团头像 -->
+          <view v-if="pinkList && pinkList.length" class="mb-20rpx bg-white px-20rpx py-20rpx text-center">
+            <image v-for="i in pinkList" :key="i" class="mx-4rpx mb-8rpx h-80rpx w-80rpx rounded-full" :src="i.avatar" />
           </view>
         </template>
-      </view>
-      <!-- 商品信息 -->
-      <wd-card type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage('/pages/productDetail/productDetail', { productId: detail?.orderInfoVO?.[0].productId })">
-        <template #title>
-          <view class="flex items-center justify-between">
-            <view class="text-28rpx text-#000">
-              {{ t('orderDetail.address.orderNo') }}:{{ detail?.orderInfoVO?.[0].orderNo }}
+        <!-- 地址 -->
+        <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-white px-24rpx py-20rpx">
+          <!-- 无地址 -->
+          <template v-if="!detail.orderAddressVO">
+            <view class="flex items-center justify-between" @click="selectAddress">
+              <view class="text-28rpx text-[var(--wot-color-theme)]">
+                {{ t('orderDetail.address.add') }}
+              </view>
+              <wd-icon name="arrow-right" color="#7D7D7D" size="28rpx" />
             </view>
-            <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === detail?.status)?.name" />
-          </view>
-        </template>
-        <view class="flex items-center gap-24rpx">
-          <view class="h-140rpx w-140rpx shrink-0">
-            <image
-              :src="detail?.orderInfoVO?.[0]?.image"
-              class="h-full w-full"
-              mode="aspectFit"
-            />
-          </view>
-          <view class="flex-1">
-            <view class="line-clamp-2 h-80rpx text-28rpx text-#000">
-              {{ detail?.orderInfoVO?.[0].productName }}
+          </template>
+          <!-- 有地址 -->
+          <template v-else>
+            <view v-if="detail.deliveryCode" class="mb-18rpx flex justify-between border-b-1 border-b-#e8e8e8 border-b-solid pb-18rpx text-24rpx">
+              <!-- 物流信息 -->
+              <view>{{ detail.deliveryName }}</view>
+              <view> DHL:{{ detail.deliveryCode || '-' }}</view>
+            </view>
+            <view class="mb-20rpx text-24rpx">
+              <text class="mr-20rpx">
+                {{ detail?.orderAddressVO?.realName }}
+              </text>
+              <text>{{ detail?.orderAddressVO?.phone }}</text>
+            </view>
+            <view class="text-22rpx text-#3A444C">
+              {{ detail?.orderAddressVO?.province }} {{ detail?.orderAddressVO?.city }} {{ detail?.orderAddressVO?.district }} {{ detail?.orderAddressVO?.detail }} {{ detail?.orderAddressVO?.postCode }}
+            </view>
+          </template>
+        </view>
+        <!-- 商品信息 -->
+        <wd-card type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage('/pages/productDetail/productDetail', { productId: detail?.orderInfoVO?.[0].productId })">
+          <template #title>
+            <view class="flex items-center justify-between">
+              <view class="text-28rpx text-#000">
+                {{ t('orderDetail.address.orderNo') }}:{{ detail?.orderInfoVO?.[0].orderNo }}
+              </view>
+              <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === detail?.status)?.name" />
             </view>
-            <view class="py-4rpx text-24rpx text-#3A444C">
-              {{ t('orderDetail.address.color') }}:{{ detail?.orderInfoVO?.[0].sku }}
+          </template>
+          <view class="flex items-center gap-24rpx">
+            <view class="h-140rpx w-140rpx shrink-0">
+              <image
+                :src="detail?.orderInfoVO?.[0]?.image"
+                class="h-full w-full"
+                mode="aspectFit"
+              />
             </view>
-            <view class="flex items-center justify-between text-24rpx">
-              <view class="text-[var(--wot-color-theme)]">
-                ৳ {{ formatNumber(detail?.orderInfoVO?.[0].price) }}
+            <view class="flex-1">
+              <view class="line-clamp-2 h-80rpx text-28rpx text-#000">
+                {{ detail?.orderInfoVO?.[0].productName }}
+              </view>
+              <view class="py-4rpx text-24rpx text-#3A444C">
+                {{ t('orderDetail.address.color') }}:{{ detail?.orderInfoVO?.[0].sku }}
               </view>
-              <view class="text-#3A444C">
-                {{ t('orderDetail.address.quantity') }}:{{ detail?.orderInfoVO?.[0].payNum }}
+              <view class="flex items-center justify-between text-24rpx">
+                <view class="text-[var(--wot-color-theme)]">
+                  ৳ {{ formatNumber(detail?.orderInfoVO?.[0].price) }}
+                </view>
+                <view class="text-#3A444C">
+                  {{ t('orderDetail.address.quantity') }}:{{ detail?.orderInfoVO?.[0].payNum }}
+                </view>
               </view>
             </view>
           </view>
-        </view>
-      </wd-card>
-      <!-- 订单信息 -->
-      <view class="bg-white px-24rpx">
-        <view class="border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
-          <view class="mb-12rpx text-28rpx">
-            {{ t('orderDetail.summary.title') }}
-          </view>
-          <view class="flex flex-col gap-16rpx text-#3A444C">
-            <view class="flex items-center justify-between text-24rpx">
-              <text>SubTotal</text>
-              <text>৳{{ formatNumber(detail.totalPrice) }}</text>
+        </wd-card>
+        <!-- 订单信息 -->
+        <view class="bg-white px-24rpx">
+          <view class="border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
+            <view class="mb-12rpx text-28rpx">
+              {{ t('orderDetail.summary.title') }}
             </view>
-          </view>
-        </view>
-        <view v-if="detail?.status !== 1" class="flex items-center justify-between border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
-          <view class="text-28rpx">
-            {{ t('orderDetail.payment.title') }}
-          </view>
-          <view class="flex flex-col gap-16rpx text-#3A444C">
-            <view class="flex items-center justify-between text-24rpx">
-              <text>BandhuBuy Wallet</text>
+            <view class="flex flex-col gap-16rpx text-#3A444C">
+              <view class="flex items-center justify-between text-24rpx">
+                <text>SubTotal</text>
+                <text>৳{{ formatNumber(detail.totalPrice) }}</text>
+              </view>
             </view>
           </view>
-        </view>
-        <view v-if="detail?.orderStatusVO?.length" class="py-24rpx">
-          <template v-for="i in detail?.orderStatusVO" :key="i.id">
-            <view v-if="timeMap[i.changeType]" class="mb-16rpx flex flex-col text-#3A444C">
+          <view v-if="detail?.status !== 1" class="flex items-center justify-between border-b-1 border-b-#e8e8e8 border-b-solid py-24rpx">
+            <view class="text-28rpx">
+              {{ t('orderDetail.payment.title') }}
+            </view>
+            <view class="flex flex-col gap-16rpx text-#3A444C">
               <view class="flex items-center justify-between text-24rpx">
-                <text>{{ timeMap[i.changeType] }}</text>
-                <text>{{ i.createTime }}</text>
+                <text>BandhuBuy Wallet</text>
               </view>
             </view>
-          </template>
+          </view>
+          <view v-if="detail?.orderStatusVO?.length" class="py-24rpx">
+            <template v-for="i in detail?.orderStatusVO" :key="i.id">
+              <view v-if="timeMap[i.changeType]" class="mb-16rpx flex flex-col text-#3A444C">
+                <view class="flex items-center justify-between text-24rpx">
+                  <text>{{ timeMap[i.changeType] }}</text>
+                  <text>{{ i.createTime }}</text>
+                </view>
+              </view>
+            </template>
+          </view>
         </view>
       </view>
-    </view>
+    </template>
+
     <template #bottom>
       <view v-if="detail?.status === 1" class="flex items-center justify-end bg-white/60 px-28rpx py-30rpx backdrop-blur-20">
         <!-- 取消订单按钮 -->
@@ -461,6 +552,7 @@ onUnmounted(() => {
         </wd-button>
       </view>
     </template>
+
     <!-- DialogBox 函数式调用 -->
     <DialogBox
       v-bind="dialogConfig"

+ 214 - 117
src/pages/productDetail/productDetail.vue

@@ -56,6 +56,7 @@ onPageScroll((e) => {
   }
 })
 const productId = ref('') // 商品id
+const isPageLoading = ref(true) // 页面加载状态
 const detail = ref<any>({
   sliderImage: '',
   flatPattern: '',
@@ -340,9 +341,15 @@ onLoad((options) => {
   productId.value = params.productId
 })
 onShow(async () => {
-  getCarousel()
-  await queryDetail()
-  await queryPinkInfo()
+  try {
+    isPageLoading.value = true
+    getCarousel()
+    await queryDetail()
+    await queryPinkInfo()
+  }
+  finally {
+    isPageLoading.value = false
+  }
 })
 </script>
 
@@ -356,143 +363,233 @@ onShow(async () => {
         </view>
       </template>
     </wd-navbar>
-    <view class="relative">
-      <wd-swiper
-        v-model:current="current" :list="detail.sliderImage.split(',')" autoplay height="750rpx"
-        custom-indicator-class="bottom-40rpx!" :indicator="{ type: 'fraction' }" indicator-position="bottom-right"
-        image-mode="aspectFit"
-      />
-      <NotificationCarousel
-        :notifications="notifications"
-        :top="`${safeAreaInsets?.top + 52}px`"
+
+    <!-- 页面加载时显示骨架屏 -->
+    <template v-if="isPageLoading">
+      <!-- 轮播图骨架屏 -->
+      <wd-skeleton
+        :row-col="[{ height: '750rpx' }]"
+        animation="gradient"
       />
-    </view>
-    <view class="relative -top-24rpx">
-      <view
-        class="flex items-center justify-between rounded-t-24rpx from-[#FF3779] to-[#FF334A] bg-gradient-to-br px-24rpx pb-24rpx pt-18rpx text-white"
-      >
-        <view>
-          <view class="mb-12rpx flex items-baseline">
-            <text class="text-28rpx">
-              {{ $t('productDetail.price') }}
-            </text>
-            <view class="ml-8rpx rounded-t-18rpx rounded-br-18rpx bg-#202221 px-12rpx text-24rpx">
-              {{ detail.people || 0 }}GB
-            </view>
-          </view>
+
+      <!-- 价格区域骨架屏 -->
+      <view class="relative -top-24rpx">
+        <view class="rounded-t-24rpx bg-white px-24rpx pb-24rpx pt-18rpx">
+          <wd-skeleton
+            :row-col="[
+              { width: '200rpx', height: '40rpx' }, // 价格标签
+              { width: '300rpx', height: '60rpx', marginTop: '12rpx' }, // 价格数值
+              { width: '150rpx', height: '32rpx', marginTop: '16rpx' }, // 销量
+            ]"
+            animation="gradient"
+          />
+        </view>
+        <view class="bg-white px-24rpx pb-24rpx pt-20rpx">
+          <wd-skeleton
+            :row-col="[
+              { width: '100%', height: '60rpx' }, // 商品标题
+              { width: '200rpx', height: '40rpx', marginTop: '16rpx' }, // 规格选择
+            ]"
+            animation="gradient"
+          />
+        </view>
+      </view>
+
+      <!-- 拼团规则骨架屏 -->
+      <view class="mb-20rpx bg-white p-24rpx">
+        <wd-skeleton
+          :row-col="[
+            { width: '200rpx', height: '40rpx' }, // 标题
+            { width: '100%', height: '200rpx', marginTop: '20rpx' }, // 图片
+          ]"
+          animation="gradient"
+        />
+      </view>
+
+      <!-- 拼团信息骨架屏 -->
+      <view class="mb-20rpx bg-white px-24rpx pt-24rpx">
+        <wd-skeleton
+          :row-col="[
+            { width: '200rpx', height: '40rpx' }, // 标题
+            // 拼团列表项
+            [
+              [
+                { width: '56rpx', height: '56rpx', type: 'circle' },
+                { width: '56rpx', height: '56rpx', type: 'circle', marginLeft: '8rpx' },
+                { width: '56rpx', height: '56rpx', type: 'circle', marginLeft: '8rpx' },
+              ],
+              { width: '200rpx', height: '28rpx', marginLeft: '16rpx' },
+              { width: '120rpx', height: '60rpx', marginLeft: 'auto' },
+            ],
+            [
+              [
+                { width: '56rpx', height: '56rpx', type: 'circle' },
+                { width: '56rpx', height: '56rpx', type: 'circle', marginLeft: '8rpx' },
+              ],
+              { width: '200rpx', height: '28rpx', marginLeft: '16rpx' },
+              { width: '120rpx', height: '60rpx', marginLeft: 'auto' },
+            ],
+          ]"
+          animation="gradient"
+        />
+      </view>
+
+      <!-- 商品详情骨架屏 -->
+      <view class="bg-white p-24rpx">
+        <wd-skeleton
+          :row-col="[
+            { width: '200rpx', height: '40rpx' }, // 标题
+            { width: '100%', height: '400rpx', marginTop: '20rpx' }, // 详情图1
+            { width: '100%', height: '400rpx', marginTop: '20rpx' }, // 详情图2
+            { width: '100%', height: '400rpx', marginTop: '20rpx' }, // 详情图3
+          ]"
+          animation="gradient"
+        />
+      </view>
+    </template>
+
+    <!-- 实际内容 -->
+    <template v-else>
+      <view class="relative">
+        <wd-swiper
+          v-model:current="current" :list="detail.sliderImage.split(',')" autoplay height="750rpx"
+          custom-indicator-class="bottom-40rpx!" :indicator="{ type: 'fraction' }" indicator-position="bottom-right"
+          image-mode="aspectFit"
+        />
+        <NotificationCarousel
+          :notifications="notifications"
+          :top="`${safeAreaInsets?.top + 52}px`"
+        />
+      </view>
+      <view class="relative -top-24rpx">
+        <view
+          class="flex items-center justify-between rounded-t-24rpx from-[#FF3779] to-[#FF334A] bg-gradient-to-br px-24rpx pb-24rpx pt-18rpx text-white"
+        >
           <view>
-            <text class="text-48rpx">
+            <view class="mb-12rpx flex items-baseline">
               <text class="text-28rpx">
-                ৳
-              </text>{{ formatNumber(detail.price) }}
-            </text>
-            <text class="ml-22rpx text-28rpx line-through">
-              ৳{{ formatNumber(detail.otPrice) }}
-            </text>
+                {{ $t('productDetail.price') }}
+              </text>
+              <view class="ml-8rpx rounded-t-18rpx rounded-br-18rpx bg-#202221 px-12rpx text-24rpx">
+                {{ detail.people || 0 }}GB
+              </view>
+            </view>
+            <view>
+              <text class="text-48rpx">
+                <text class="text-28rpx">
+                  ৳
+                </text>{{ formatNumber(detail.price) }}
+              </text>
+              <text class="ml-22rpx text-28rpx line-through">
+                ৳{{ formatNumber(detail.otPrice) }}
+              </text>
+            </view>
           </view>
+          <text class="text-28rpx">
+            {{ t('productDetail.sold', [detail.sales]) }}
+          </text>
         </view>
-        <text class="text-28rpx">
-          {{ t('productDetail.sold', [detail.sales]) }}
-        </text>
-      </view>
-      <view class="bg-white px-24rpx pb-24rpx pt-20rpx text-32rpx">
-        <view class="line-clamp-2font-bold mb-16rpx">
-          {{ detail.storeName }}
+        <view class="bg-white px-24rpx pb-24rpx pt-20rpx text-32rpx">
+          <view class="line-clamp-2font-bold mb-16rpx">
+            {{ detail.storeName }}
+          </view>
+          <view class="flex items-center justify-between" @click="openSku('open')">
+            <view>
+              <text class="text-28rpx text-#757575">
+                {{ selectedSpecsText }}
+              </text>
+            </view>
+            <wd-icon name="arrow-right" color="#7D7D7D" size="36rpx" />
+          </view>
         </view>
-        <view class="flex items-center justify-between" @click="openSku('open')">
-          <view>
-            <text class="text-28rpx text-#757575">
-              {{ selectedSpecsText }}
+      </view>
+      <view class="mb-20rpx bg-white p-24rpx">
+        <view class="mb-20rpx flex items-center justify-between">
+          <view
+            class="flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
+          >
+            <text class="ml-10rpx text-32rpx">
+              {{ $t('productDetail.groupRules') }}
+            </text>
+          </view>
+          <view class="flex items-center" @click="toPage('/pages/webLink/webLink', { title: $t('productDetail.viewRules'), link: 'http://' })">
+            <text class="mr-8rpx text-24rpx text-#3A444C">
+              {{ $t('productDetail.viewRules') }}
             </text>
+            <wd-icon name="arrow-right" color="#7D7D7D" size="24rpx" />
           </view>
-          <wd-icon name="arrow-right" color="#7D7D7D" size="36rpx" />
         </view>
+        <image src="/static/images/buy-flow.png" class="w-full" mode="widthFix" />
       </view>
-    </view>
-    <view class="mb-20rpx bg-white p-24rpx">
-      <view class="mb-20rpx flex items-center justify-between">
+      <view v-if="pinkInfo && pinkInfo.length" class="mb-20rpx bg-white px-24rpx pt-24rpx">
         <view
-          class="flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
+          class="mb-20rpx flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
         >
           <text class="ml-10rpx text-32rpx">
-            {{ $t('productDetail.groupRules') }}
-          </text>
-        </view>
-        <view class="flex items-center" @click="toPage('/pages/webLink/webLink', { title: $t('productDetail.viewRules'), link: 'http://' })">
-          <text class="mr-8rpx text-24rpx text-#3A444C">
-            {{ $t('productDetail.viewRules') }}
+            {{ $t('productDetail.ongoingGroup') }}
           </text>
-          <wd-icon name="arrow-right" color="#7D7D7D" size="24rpx" />
         </view>
-      </view>
-      <image src="/static/images/buy-flow.png" class="w-full" mode="widthFix" />
-    </view>
-    <view v-if="pinkInfo && pinkInfo.length" class="mb-20rpx bg-white px-24rpx pt-24rpx">
-      <view
-        class="mb-20rpx flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
-      >
-        <text class="ml-10rpx text-32rpx">
-          {{ $t('productDetail.ongoingGroup') }}
-        </text>
-      </view>
-      <swiper
-        autoplay
-        vertical
-        circular
-        class="py-10rpx"
-        :style="{ height: pinkInfo[0].length <= 3 ? `${pinkInfo[0].length * 80}rpx` : '240rpx' }"
-      >
-        <swiper-item v-for="(list, y) in pinkInfo" :key="y">
-          <view class="flex flex-col gap-24rpx">
-            <view v-for="(item, index) in list" :key="index" class="flex items-center justify-between">
-              <view class="flex items-center">
-                <view>
-                  <!-- 头像组 最多五个 -->
-                  <view class="mr-16rpx min-w-220rpx flex items-center">
-                    <view
-                      v-for="(e, i) in item.successAvatar.slice(0, 5)"
-                      :key="i"
-                      :style="{ marginLeft: i !== 0 ? '-20rpx' : '0', zIndex: 10 - i }"
-                      class="h-56rpx w-56rpx overflow-hidden border-2rpx border-white rounded-full border-solid"
-                    >
-                      <image :src="e ? e : '/static/images/default-avatar.png'" class="h-full w-full" mode="aspectFill" />
+        <swiper
+          autoplay
+          vertical
+          circular
+          class="py-10rpx"
+          :style="{ height: pinkInfo[0].length <= 3 ? `${pinkInfo[0].length * 80}rpx` : '240rpx' }"
+        >
+          <swiper-item v-for="(list, y) in pinkInfo" :key="y">
+            <view class="flex flex-col gap-24rpx">
+              <view v-for="(item, index) in list" :key="index" class="flex items-center justify-between">
+                <view class="flex items-center">
+                  <view>
+                    <!-- 头像组 最多五个 -->
+                    <view class="mr-16rpx min-w-220rpx flex items-center">
+                      <view
+                        v-for="(e, i) in item.successAvatar.slice(0, 5)"
+                        :key="i"
+                        :style="{ marginLeft: i !== 0 ? '-20rpx' : '0', zIndex: 10 - i }"
+                        class="h-56rpx w-56rpx overflow-hidden border-2rpx border-white rounded-full border-solid"
+                      >
+                        <image :src="e ? e : '/static/images/default-avatar.png'" class="h-full w-full" mode="aspectFill" />
+                      </view>
                     </view>
                   </view>
-                </view>
-                <view>
-                  <view class="text-28rpx">
-                    {{ $t('productDetail.need') }}
-                    <text class="text-[var(--wot-color-theme)]">
-                      {{ item.totalNum - item.remainNum }}
-                    </text>
-                    {{ $t('productDetail.more') }}
+                  <view>
+                    <view class="text-28rpx">
+                      {{ $t('productDetail.need') }}
+                      <text class="text-[var(--wot-color-theme)]">
+                        {{ item.totalNum - item.remainNum }}
+                      </text>
+                      {{ $t('productDetail.more') }}
+                    </view>
                   </view>
                 </view>
+                <wd-button size="small" @click="openSku('join', item.id, item.orderId)">
+                  {{ $t('productDetail.joinGroup') }}
+                </wd-button>
               </view>
-              <wd-button size="small" @click="openSku('join', item.id, item.orderId)">
-                {{ $t('productDetail.joinGroup') }}
-              </wd-button>
             </view>
-          </view>
-        </swiper-item>
-      </swiper>
-    </view>
-    <view class="bg-white p-24rpx">
-      <view
-        class="mb-20rpx flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
-      >
-        <text class="ml-10rpx text-32rpx">
-          {{ $t('productDetail.details') }}
-        </text>
+          </swiper-item>
+        </swiper>
       </view>
-      <view v-for="i in detail.flatPattern.split(',')" :key="i">
-        <image
-          :src="i"
-          mode="widthFix"
-          class="w-full"
-        />
+      <view class="bg-white p-24rpx">
+        <view
+          class="mb-20rpx flex items-center before:h-45rpx before:w-8rpx before:rounded-4rpx before:bg-#FF3778 before:content-empty"
+        >
+          <text class="ml-10rpx text-32rpx">
+            {{ $t('productDetail.details') }}
+          </text>
+        </view>
+        <view v-for="i in detail.flatPattern.split(',')" :key="i">
+          <image
+            :src="i"
+            mode="widthFix"
+            class="w-full"
+          />
+        </view>
       </view>
-    </view>
+    </template>
+
+    <!-- 底部按钮区域 -->
     <template #bottom>
       <view class="flex bg-white/60 px-28rpx py-30rpx backdrop-blur-20">
         <view class="mr-30rpx flex flex-1 items-center justify-around gap-20rpx">