zlwu 6 months ago
parent
commit
ae1f4490bc
2 changed files with 235 additions and 231 deletions
  1. 34 49
      miniprogram/src/pages/client/home/home.vue
  2. 201 182
      sqxp-wx/pages/client/home/home.vue

+ 34 - 49
miniprogram/src/pages/client/home/home.vue

@@ -21,12 +21,10 @@ const SIDEBAR_PAGE_SIZE = 30
 const ROOT_CATEGORY_PAGE_SIZE = 20
 const ROOT_CATEGORY_PAGE_SIZE = 20
 const PRODUCT_PAGE_SIZE = 20
 const PRODUCT_PAGE_SIZE = 20
 
 
-// 状态(一级分类由接口分页拉全;左侧栏由 sidebar 接口分页:顶为「全部」时为全部+一级,顶为某一级时为全部+二级,首行展示名由前端替换为一级名称)
+// 状态(一级分类由接口分页拉全;左侧栏 sidebar 接口按页循环拉全,顶为「全部」时为全部+一级,顶为某一级时为全部+二级)
 const rootCategoryList = ref<Array<{ id: string; name: string; imageUrl: string }>>([])
 const rootCategoryList = ref<Array<{ id: string; name: string; imageUrl: string }>>([])
 const sidebarRows = ref<{ id: string; name: string }[]>([])
 const sidebarRows = ref<{ id: string; name: string }[]>([])
 const sidebarLoading = ref(false)
 const sidebarLoading = ref(false)
-const sidebarHasMore = ref(false)
-const nextSidebarPage = ref(1)
 const products = ref<Product[]>([])
 const products = ref<Product[]>([])
 const selectedFirstCategoryId = ref<'all' | string>('all')
 const selectedFirstCategoryId = ref<'all' | string>('all')
 const selectedSecondCategoryId = ref<'all' | string>('all')
 const selectedSecondCategoryId = ref<'all' | string>('all')
@@ -228,51 +226,45 @@ async function loadAllRootCategories() {
   }
   }
 }
 }
 
 
+/** 侧栏分类按接口分页循环拉全 */
 async function reloadSidebar() {
 async function reloadSidebar() {
-  sidebarRows.value = []
-  nextSidebarPage.value = 1
-  sidebarHasMore.value = true
-  await loadSidebarPage(true)
-}
-
-async function loadSidebarPage(isFirst: boolean) {
-  if (sidebarLoading.value) return
   const token = uni.getStorageSync(TOKEN_KEY)
   const token = uni.getStorageSync(TOKEN_KEY)
-  if (!token) return
-  const page = isFirst ? 1 : nextSidebarPage.value
-  if (!isFirst && !sidebarHasMore.value) return
+  if (!token) {
+    sidebarRows.value = []
+    return
+  }
+  if (sidebarLoading.value) return
+  sidebarRows.value = []
   sidebarLoading.value = true
   sidebarLoading.value = true
   try {
   try {
-    const res: any = await request({
-      url: '/mini/client/product-categories/sidebar',
-      method: 'GET',
-      data: {
-        firstCategoryId: selectedFirstCategoryId.value,
-        page,
-        pageSize: SIDEBAR_PAGE_SIZE
+    let page = 1
+    while (true) {
+      const res: any = await request({
+        url: '/mini/client/product-categories/sidebar',
+        method: 'GET',
+        data: {
+          firstCategoryId: selectedFirstCategoryId.value,
+          page,
+          pageSize: SIDEBAR_PAGE_SIZE
+        }
+      })
+      if (res.data.code !== 200) break
+      const d = res.data.data || {}
+      const list = (d.list || []) as { id: string; name: string }[]
+      for (const row of list) {
+        sidebarRows.value.push(row)
       }
       }
-    })
-    if (res.data.code !== 200) return
-    const d = res.data.data || {}
-    const list = (d.list || []) as { id: string; name: string }[]
-    if (isFirst) {
-      sidebarRows.value = list
-      nextSidebarPage.value = 2
-    } else {
-      sidebarRows.value = [...sidebarRows.value, ...list]
-      nextSidebarPage.value = page + 1
+      const totalPages = Number(d.totalPages) || 0
+      if (page >= totalPages || list.length === 0) break
+      page += 1
     }
     }
-    const totalPages = Number(d.totalPages) || 0
-    sidebarHasMore.value = page < totalPages
+  } catch (error) {
+    console.error('加载侧栏分类失败:', error)
   } finally {
   } finally {
     sidebarLoading.value = false
     sidebarLoading.value = false
   }
   }
 }
 }
 
 
-function onCategoryScrollToLower() {
-  void loadSidebarPage(false)
-}
-
 let keywordDebounce: ReturnType<typeof setTimeout> | null = null
 let keywordDebounce: ReturnType<typeof setTimeout> | null = null
 watch(keyword, () => {
 watch(keyword, () => {
   if (keywordDebounce) clearTimeout(keywordDebounce)
   if (keywordDebounce) clearTimeout(keywordDebounce)
@@ -484,13 +476,7 @@ onShow(() => {
 
 
       <view class="product-layout">
       <view class="product-layout">
         <view class="product-body">
         <view class="product-body">
-          <scroll-view
-            scroll-y
-            class="category-pane"
-            :show-scrollbar="true"
-            :lower-threshold="60"
-            @scrolltolower="onCategoryScrollToLower"
-          >
+          <scroll-view scroll-y class="category-pane" :show-scrollbar="true">
             <view class="category-scroll-inner">
             <view class="category-scroll-inner">
               <view
               <view
                 v-for="cat in displaySidebarRows"
                 v-for="cat in displaySidebarRows"
@@ -504,7 +490,6 @@ onShow(() => {
                 {{ cat.name }}
                 {{ cat.name }}
               </view>
               </view>
               <view v-if="sidebarLoading && sidebarRows.length === 0" class="pane-footer-tip">加载中...</view>
               <view v-if="sidebarLoading && sidebarRows.length === 0" class="pane-footer-tip">加载中...</view>
-              <view v-else-if="sidebarHasMore" class="pane-footer-tip">上拉加载更多分类</view>
             </view>
             </view>
           </scroll-view>
           </scroll-view>
 
 
@@ -1161,13 +1146,13 @@ page {
   padding: 0 24rpx;
   padding: 0 24rpx;
   box-sizing: border-box;
   box-sizing: border-box;
   overflow: visible;
   overflow: visible;
-  /* demo .cart-bar bottom:72px — 在 tab 之上留出空隙;与 uni --window-bottom 叠加 */
+  /* 紧贴 tab 上沿:略留一点避免与底栏视觉粘连 */
   /* #ifdef H5 */
   /* #ifdef H5 */
-  bottom: calc(var(--window-bottom, calc(50px + env(safe-area-inset-bottom, 0px))) + 44rpx);
+  bottom: calc(var(--window-bottom, calc(50px + env(safe-area-inset-bottom, 0px))) + 10rpx);
   /* #endif */
   /* #endif */
   /* #ifndef H5 */
   /* #ifndef H5 */
-  bottom: calc(100rpx + constant(safe-area-inset-bottom));
-  bottom: calc(100rpx + env(safe-area-inset-bottom, 0px));
+  bottom: calc(92rpx + constant(safe-area-inset-bottom));
+  bottom: calc(92rpx + env(safe-area-inset-bottom, 0px));
   /* #endif */
   /* #endif */
 }
 }
 
 

+ 201 - 182
sqxp-wx/pages/client/home/home.vue

@@ -33,135 +33,142 @@
         </view>
         </view>
       </view>
       </view>
 
 
-      <!-- 一级分类及以下在灰底区域,独立白卡片(与 demo .section + .first-level 一致) -->
-      <view class="category-section">
-        <view class="section-head">
-          <text class="section-title">商品分类</text>
-        </view>
-
-        <view class="first-category-wrap">
-          <swiper
-            class="first-category-swiper"
-            :current="firstCategorySwiperIndex"
-            :circular="false"
-            :indicator-dots="false"
-            @change="onFirstCategorySwiperChange"
-          >
-            <swiper-item v-for="(page, pageIdx) in firstCategoryPages" :key="`category-page-${pageIdx}`">
-              <view class="first-category-grid">
-                <view
-                  v-for="cat in page"
-                  :key="cat.id"
-                  class="first-category-card"
-                  :class="{ active: selectedFirstCategoryId === cat.id }"
-                  hover-class="first-category-card--hover"
-                  :hover-stay-time="120"
-                  @click="handleFirstCategoryClick(cat.id)"
-                >
-                  <ClientImageLoader
-                    class="first-category-image"
-                    :src="cat.imageUrl || DEFAULT_PLACEHOLDER_IMAGE_URL"
-                    mode="aspectFill"
-                    radius="24rpx"
-                  />
-                  <text class="first-category-name">{{ cat.name }}</text>
-                </view>
-              </view>
-            </swiper-item>
-          </swiper>
-        </view>
+      <!-- 一级分类 + 侧栏/商品区:整体纵向滚动,触底加载商品分页(及侧栏分页) -->
+      <scroll-view
+        scroll-y
+        class="home-bottom-scroll"
+        :show-scrollbar="true"
+        :enable-flex="true"
+        :lower-threshold="80"
+        @scrolltolower="onHomeBottomScrollToLower"
+      >
+        <view class="home-bottom-scroll-inner">
+          <!-- 与顶栏一致:scroll-view 抵消 content-wrap 左右 32rpx;分类区单独缩进保持原视觉 -->
+          <view class="home-bottom-category-pad">
+          <!-- 一级分类及以下在灰底区域,独立白卡片(与 demo .section + .first-level 一致) -->
+          <view class="category-section">
+            <view class="section-head">
+              <text class="section-title">商品分类</text>
+            </view>
 
 
-        <view v-if="firstCategoryPages.length > 0" class="first-level-indicator">
-          <view
-            v-for="(_, idx) in firstCategoryPages"
-            :key="`dot-${idx}`"
-            class="indicator-dot"
-            :class="{ active: firstCategorySwiperIndex === idx }"
-            @click="goFirstCategorySwiperPage(idx)"
-          />
-        </view>
-      </view>
+            <view class="first-category-wrap">
+              <swiper
+                class="first-category-swiper"
+                :current="firstCategorySwiperIndex"
+                :circular="false"
+                :indicator-dots="false"
+                @change="onFirstCategorySwiperChange"
+              >
+                <swiper-item v-for="(page, pageIdx) in firstCategoryPages" :key="`category-page-${pageIdx}`">
+                  <view class="first-category-grid">
+                    <view
+                      v-for="cat in page"
+                      :key="cat.id"
+                      class="first-category-card"
+                      :class="{ active: selectedFirstCategoryId === cat.id }"
+                      hover-class="first-category-card--hover"
+                      :hover-stay-time="120"
+                      @click="handleFirstCategoryClick(cat.id)"
+                    >
+                      <ClientImageLoader
+                        class="first-category-image"
+                        :src="cat.imageUrl || DEFAULT_PLACEHOLDER_IMAGE_URL"
+                        mode="aspectFill"
+                        radius="24rpx"
+                      />
+                      <text class="first-category-name">{{ cat.name }}</text>
+                    </view>
+                  </view>
+                </swiper-item>
+              </swiper>
+            </view>
 
 
-      <view class="product-layout">
-        <view class="product-body">
-          <scroll-view
-            scroll-y
-            class="category-pane"
-            :show-scrollbar="true"
-            :lower-threshold="60"
-            @scrolltolower="onCategoryScrollToLower"
-          >
-            <view class="category-scroll-inner">
+            <view v-if="firstCategoryPages.length > 0" class="first-level-indicator">
               <view
               <view
-                v-for="cat in displaySidebarRows"
-                :key="cat.id"
-                class="category-item"
-                :class="{ active: selectedSecondCategoryId === cat.id }"
-                hover-class="category-item--hover"
-                :hover-stay-time="120"
-                @click="selectedSecondCategoryId = cat.id"
-              >
-                {{ cat.name }}
-              </view>
-              <view v-if="sidebarLoading && sidebarRows.length === 0" class="pane-footer-tip">加载中...</view>
-              <view v-else-if="sidebarHasMore" class="pane-footer-tip">上拉加载更多分类</view>
+                v-for="(_, idx) in firstCategoryPages"
+                :key="`dot-${idx}`"
+                class="indicator-dot"
+                :class="{ active: firstCategorySwiperIndex === idx }"
+                @click="goFirstCategorySwiperPage(idx)"
+              />
             </view>
             </view>
-          </scroll-view>
-
-          <view class="goods-pane">
-            <scroll-view
-              scroll-y
-              class="goods-scroll"
-              :show-scrollbar="true"
-              :lower-threshold="80"
-              @scrolltolower="onGoodsScrollToLower"
-            >
-              <view class="goods-scroll-inner">
-                <view
-                  v-for="product in products"
-                  :key="product.id"
-                  class="goods-card"
-                  hover-class="goods-card--pressed"
-                  :hover-stay-time="120"
-                  @click="goProductView(product.id)"
-                >
-                  <ClientImageLoader
-                    class="goods-image"
-                    :src="product.imageUrl || IMG_DEFAULT_PRODUCT"
-                    mode="aspectFill"
-                    radius="16rpx"
-                  />
-                  <view class="goods-main">
-                    <text class="goods-name">{{ product.name || '-' }}</text>
-                    <text class="goods-spec">{{ formatMeasureText(product.measureValue, product.unitName) }}</text>
-                    <view class="bottom-row">
-                      <text class="goods-price">¥{{ formatPrice(product.price || 0) }}</text>
-                      <view class="bottom-row-actions">
-                        <view v-if="getCartQuantity(product.id) === 0" class="add-btn" @click.stop="addToCart(product)">
-                          <text class="add-btn-plus">+</text>
-                        </view>
-                        <view v-else class="quantity-control" @click.stop>
-                          <view class="qty-btn" @click.stop="updateQuantity(product.id, -1)">
-                            <text>-</text>
-                          </view>
-                          <text class="qty-num">{{ getCartQuantity(product.id) }}</text>
-                          <view class="qty-btn" @click.stop="updateQuantity(product.id, 1)">
-                            <text>+</text>
+          </view>
+          </view>
+
+          <view class="product-layout">
+            <view class="product-body">
+              <view class="category-pane">
+                <view class="category-scroll-inner">
+                  <view
+                    v-for="cat in displaySidebarRows"
+                    :key="cat.id"
+                    class="category-item"
+                    :class="{ active: selectedSecondCategoryId === cat.id }"
+                    hover-class="category-item--hover"
+                    :hover-stay-time="120"
+                    @click="selectedSecondCategoryId = cat.id"
+                  >
+                    {{ cat.name }}
+                  </view>
+                  <view v-if="sidebarLoading && sidebarRows.length === 0" class="pane-footer-tip">加载中...</view>
+                </view>
+              </view>
+
+              <view class="goods-pane">
+                <view class="goods-scroll">
+                  <view class="goods-scroll-inner">
+                    <view
+                      v-for="product in products"
+                      :key="product.id"
+                      class="goods-card"
+                      hover-class="goods-card--pressed"
+                      :hover-stay-time="120"
+                      @click="goProductView(product.id)"
+                    >
+                      <ClientImageLoader
+                        class="goods-image"
+                        :src="product.imageUrl || IMG_DEFAULT_PRODUCT"
+                        mode="aspectFill"
+                        radius="16rpx"
+                      />
+                      <view class="goods-main">
+                        <text class="goods-name">{{ product.name || '-' }}</text>
+                        <text class="goods-spec">{{ formatMeasureText(product.measureValue, product.unitName) }}</text>
+                        <view class="bottom-row">
+                          <text class="goods-price">¥{{ formatPrice(product.price || 0) }}</text>
+                          <view class="bottom-row-actions">
+                            <view v-if="getCartQuantity(product.id) === 0" class="add-btn" @click.stop="addToCart(product)">
+                              <text class="add-btn-plus">+</text>
+                            </view>
+                            <view v-else class="quantity-control" @click.stop>
+                              <view class="qty-btn" @click.stop="updateQuantity(product.id, -1)">
+                                <text>-</text>
+                              </view>
+                              <text class="qty-num">{{ getCartQuantity(product.id) }}</text>
+                              <view class="qty-btn" @click.stop="updateQuantity(product.id, 1)">
+                                <text>+</text>
+                              </view>
+                            </view>
                           </view>
                           </view>
                         </view>
                         </view>
                       </view>
                       </view>
                     </view>
                     </view>
+
+                    <view v-if="products.length === 0 && !productsLoadingMore" class="empty-list-tip">暂无匹配商品</view>
+                    <view v-if="productsLoadingMore" class="pane-footer-tip">加载中...</view>
+                    <view
+                      v-else-if="products.length > 0 && !productHasMore"
+                      class="pane-footer-tip pane-footer-tip--nomore"
+                    >
+                      没有更多了
+                    </view>
                   </view>
                   </view>
                 </view>
                 </view>
-
-                <view v-if="products.length === 0 && !productsLoadingMore" class="empty-list-tip">暂无匹配商品</view>
-                <view v-if="productsLoadingMore" class="pane-footer-tip">加载中...</view>
-                <view v-else-if="products.length > 0 && !productHasMore" class="pane-footer-tip">没有更多了</view>
               </view>
               </view>
-            </scroll-view>
+            </view>
           </view>
           </view>
         </view>
         </view>
-      </view>
+      </scroll-view>
     </view>
     </view>
   </view>
   </view>
 
 
@@ -217,12 +224,10 @@ const SIDEBAR_PAGE_SIZE = 30
 const ROOT_CATEGORY_PAGE_SIZE = 20
 const ROOT_CATEGORY_PAGE_SIZE = 20
 const PRODUCT_PAGE_SIZE = 20
 const PRODUCT_PAGE_SIZE = 20
 
 
-// 状态(一级分类由接口分页拉全;左侧栏由 sidebar 接口分页:顶为「全部」时为全部+一级,顶为某一级时为全部+二级,首行展示名由前端替换为一级名称)
+// 状态(一级分类由接口分页拉全;左侧栏 sidebar 按页循环拉全;顶为「全部」时为全部+一级,顶为某一级时为全部+二级,首行展示名由前端替换为一级名称)
 const rootCategoryList = ref([])
 const rootCategoryList = ref([])
 const sidebarRows = ref([])
 const sidebarRows = ref([])
 const sidebarLoading = ref(false)
 const sidebarLoading = ref(false)
-const sidebarHasMore = ref(false)
-const nextSidebarPage = ref(1)
 const products = ref([])
 const products = ref([])
 const selectedFirstCategoryId = ref('all')
 const selectedFirstCategoryId = ref('all')
 const selectedSecondCategoryId = ref('all')
 const selectedSecondCategoryId = ref('all')
@@ -374,7 +379,8 @@ async function loadMoreProducts() {
   }
   }
 }
 }
 
 
-function onGoodsScrollToLower() {
+/** 下部分整体滚动触底:仅商品分页(侧栏二级分类在 reloadSidebar 时已一次性拉全) */
+function onHomeBottomScrollToLower() {
   void loadMoreProducts()
   void loadMoreProducts()
 }
 }
 
 
@@ -410,52 +416,46 @@ async function loadAllRootCategories() {
   }
   }
 }
 }
 
 
+/** 侧栏(二级/筛选项)按接口分页循环拉全,与一级分类 loadAllRootCategories 策略一致 */
 async function reloadSidebar() {
 async function reloadSidebar() {
-  sidebarRows.value = []
-  nextSidebarPage.value = 1
-  sidebarHasMore.value = true
-  await loadSidebarPage(true)
-}
-
-async function loadSidebarPage(isFirst) {
-  if (sidebarLoading.value) return
   const token = uni.getStorageSync(TOKEN_KEY)
   const token = uni.getStorageSync(TOKEN_KEY)
-  if (!token) return
-  const page = isFirst ? 1 : nextSidebarPage.value
-  if (!isFirst && !sidebarHasMore.value) return
+  if (!token) {
+    sidebarRows.value = []
+    return
+  }
+  if (sidebarLoading.value) return
+  sidebarRows.value = []
   sidebarLoading.value = true
   sidebarLoading.value = true
   try {
   try {
-    const res = await request({
-      url: '/mini/client/product-categories/sidebar',
-      method: 'GET',
-      data: {
-        firstCategoryId: selectedFirstCategoryId.value,
-        page,
-        pageSize: SIDEBAR_PAGE_SIZE
+    let page = 1
+    while (true) {
+      const res = await request({
+        url: '/mini/client/product-categories/sidebar',
+        method: 'GET',
+        data: {
+          firstCategoryId: selectedFirstCategoryId.value,
+          page,
+          pageSize: SIDEBAR_PAGE_SIZE
+        }
+      })
+      const body = parseResponseJson(res)
+      if (body.code !== 200) break
+      const d = body.data || {}
+      const list = d.list || []
+      for (const row of list) {
+        sidebarRows.value.push(row)
       }
       }
-    })
-    const body = parseResponseJson(res)
-    if (body.code !== 200) return
-    const d = body.data || {}
-    const list = d.list || []
-    if (isFirst) {
-      sidebarRows.value = list
-      nextSidebarPage.value = 2
-    } else {
-      sidebarRows.value = [...sidebarRows.value, ...list]
-      nextSidebarPage.value = page + 1
+      const totalPages = Number(d.totalPages) || 0
+      if (page >= totalPages || list.length === 0) break
+      page += 1
     }
     }
-    const totalPages = Number(d.totalPages) || 0
-    sidebarHasMore.value = page < totalPages
+  } catch (error) {
+    console.error('加载侧栏分类失败:', error)
   } finally {
   } finally {
     sidebarLoading.value = false
     sidebarLoading.value = false
   }
   }
 }
 }
 
 
-function onCategoryScrollToLower() {
-  void loadSidebarPage(false)
-}
-
 let keywordDebounce = null
 let keywordDebounce = null
 watch(keyword, () => {
 watch(keyword, () => {
   if (keywordDebounce) clearTimeout(keywordDebounce)
   if (keywordDebounce) clearTimeout(keywordDebounce)
@@ -609,16 +609,15 @@ page {
   position: relative;
   position: relative;
   width: 100%;
   width: 100%;
   min-height: 100%;
   min-height: 100%;
-  /* 底部固定购物车条占位:按实际条高+离底间距预留,避免小屏遮挡最后一张商品卡片 */
-  --home-goods-scroll-pad: calc(360rpx + constant(safe-area-inset-bottom));
-  --home-goods-scroll-pad: calc(360rpx + env(safe-area-inset-bottom, 0px));
+  /* 底部占位:略大于结算条+tab 占用高度即可;再大只会拉长「没有更多了」与结算条之间的灰区 */
+  --home-goods-scroll-pad: calc(96rpx + constant(safe-area-inset-bottom));
+  --home-goods-scroll-pad: calc(96rpx + env(safe-area-inset-bottom, 0px));
 }
 }
 
 
 /* #ifdef H5 */
 /* #ifdef H5 */
 .home-root {
 .home-root {
-  /* H5 的 cart-bar 会叠加 --window-bottom 与额外偏移,需要更大的滚动底部留白 */
-  --home-goods-scroll-pad: calc(420rpx + constant(safe-area-inset-bottom));
-  --home-goods-scroll-pad: calc(420rpx + env(safe-area-inset-bottom, 0px));
+  --home-goods-scroll-pad: calc(132rpx + constant(safe-area-inset-bottom));
+  --home-goods-scroll-pad: calc(132rpx + env(safe-area-inset-bottom, 0px));
 }
 }
 /* #endif */
 /* #endif */
 
 
@@ -670,6 +669,28 @@ page {
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
+/* 头部以下整块纵向滚动(需占满剩余高度 scroll-view 才能滚动) */
+/* 与 .filter-top-wrap 同理:先拉满 inquiry 内容宽,避免子级负 margin 在 scroll-view 内被裁切导致商品区变窄 */
+.home-bottom-scroll {
+  flex: 1;
+  min-height: 0;
+  margin: 0 -32rpx;
+  width: calc(100% + 64rpx);
+  max-width: calc(100% + 64rpx);
+  box-sizing: border-box;
+}
+
+.home-bottom-scroll-inner {
+  width: 100%;
+  padding-bottom: var(--home-goods-scroll-pad);
+  box-sizing: border-box;
+}
+
+.home-bottom-category-pad {
+  padding: 0 32rpx;
+  box-sizing: border-box;
+}
+
 /* 对齐 mobile-ui/sanqiang-client-ui-demo.html .top */
 /* 对齐 mobile-ui/sanqiang-client-ui-demo.html .top */
 .filter-top-wrap {
 .filter-top-wrap {
   flex-shrink: 0;
   flex-shrink: 0;
@@ -882,21 +903,17 @@ page {
 }
 }
 
 
 .product-layout {
 .product-layout {
-  flex: 1;
-  min-height: 0;
+  /* 随下部分 scroll 内容增高,不再单独占满一屏 */
   /* demo .category-layout margin-top 10px */
   /* demo .category-layout margin-top 10px */
   margin-top: 20rpx;
   margin-top: 20rpx;
-  /* 二级分类 + 商品区通栏:抵消 content-wrap 左右 32rpx,商品列更宽 */
-  margin-left: -32rpx;
-  margin-right: -32rpx;
-  width: auto;
-  align-self: stretch;
+  /* 宽度已由 .home-bottom-scroll 拉满,此处不再用负 margin(在 scroll-view 内易失效) */
+  width: 100%;
+  box-sizing: border-box;
 }
 }
 
 
 .product-body {
 .product-body {
   display: flex;
   display: flex;
-  height: 100%;
-  min-height: 0;
+  align-items: stretch;
   /* demo .category-layout min-height 440px */
   /* demo .category-layout min-height 440px */
   min-height: 880rpx;
   min-height: 880rpx;
   border: 1rpx solid var(--c-border);
   border: 1rpx solid var(--c-border);
@@ -909,7 +926,7 @@ page {
   /* demo grid 92px 侧栏 */
   /* demo grid 92px 侧栏 */
   width: 184rpx;
   width: 184rpx;
   flex-shrink: 0;
   flex-shrink: 0;
-  height: 100%;
+  align-self: stretch;
   background: #f8fcfa;
   background: #f8fcfa;
   border-right: 1rpx solid #edf5f1;
   border-right: 1rpx solid #edf5f1;
   border-top-left-radius: var(--c-radius-lg);
   border-top-left-radius: var(--c-radius-lg);
@@ -917,7 +934,6 @@ page {
 }
 }
 
 
 .category-scroll-inner {
 .category-scroll-inner {
-  padding-bottom: var(--home-goods-scroll-pad);
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
@@ -954,23 +970,20 @@ page {
   padding: 16rpx 0 0;
   padding: 16rpx 0 0;
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  min-height: 0;
-  height: 100%;
+  align-self: stretch;
   border-top-right-radius: var(--c-radius-lg);
   border-top-right-radius: var(--c-radius-lg);
   border-bottom-right-radius: var(--c-radius-lg);
   border-bottom-right-radius: var(--c-radius-lg);
 }
 }
 
 
 .goods-scroll {
 .goods-scroll {
   flex: 1;
   flex: 1;
-  min-height: 0;
-  height: 100%;
+  min-width: 0;
 }
 }
 
 
 /* 内层包裹:各端对 scroll-view 直接 padding 支持不一致,用内层垫高更稳 */
 /* 内层包裹:各端对 scroll-view 直接 padding 支持不一致,用内层垫高更稳 */
 .goods-scroll-inner {
 .goods-scroll-inner {
   padding-left: 20rpx;
   padding-left: 20rpx;
   padding-right: 20rpx;
   padding-right: 20rpx;
-  padding-bottom: var(--home-goods-scroll-pad);
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
@@ -978,7 +991,13 @@ page {
   text-align: center;
   text-align: center;
   color: #9ba8a2;
   color: #9ba8a2;
   font-size: 24rpx;
   font-size: 24rpx;
-  padding: 24rpx 12rpx 32rpx;
+  padding: 20rpx 12rpx 12rpx;
+}
+
+/* 列表结束文案:收紧与下方滚动留白的间距 */
+.pane-footer-tip--nomore {
+  padding-top: 12rpx;
+  padding-bottom: 4rpx;
 }
 }
 
 
 .empty-list-tip {
 .empty-list-tip {
@@ -1152,13 +1171,13 @@ page {
   padding: 0 24rpx;
   padding: 0 24rpx;
   box-sizing: border-box;
   box-sizing: border-box;
   overflow: visible;
   overflow: visible;
-  /* demo .cart-bar bottom:72px — 在 tab 之上留出空隙;与 uni --window-bottom 叠加 */
+  /* 紧贴 tab 上沿:略留一点避免与底栏视觉粘连(H5 原 +44rpx 空隙偏大) */
   /* #ifdef H5 */
   /* #ifdef H5 */
-  bottom: calc(var(--window-bottom, calc(50px + env(safe-area-inset-bottom, 0px))) + 44rpx);
+  bottom: calc(var(--window-bottom, calc(50px + env(safe-area-inset-bottom, 0px))) + 10rpx);
   /* #endif */
   /* #endif */
   /* #ifndef H5 */
   /* #ifndef H5 */
-  bottom: calc(100rpx + constant(safe-area-inset-bottom));
-  bottom: calc(100rpx + env(safe-area-inset-bottom, 0px));
+  bottom: calc(92rpx + constant(safe-area-inset-bottom));
+  bottom: calc(92rpx + env(safe-area-inset-bottom, 0px));
   /* #endif */
   /* #endif */
 }
 }