|
|
@@ -14,8 +14,13 @@
|
|
|
/>
|
|
|
<view v-if="loading" class="home-loading">加载中...</view>
|
|
|
<block v-else-if="pageData">
|
|
|
- <block v-for="mod in modules" :key="mod.key">
|
|
|
- <template v-if="mod.enabled == 1">
|
|
|
+ <block v-for="(mod, mIndex) in enabledModules" :key="mod.key">
|
|
|
+ <!--
|
|
|
+ 超出渲染范围的模块不渲染任何节点(连占位高度都不留)。
|
|
|
+ 留占位高度会把底部哨兵推出视口,加载器误判「内容已够长」而停止挂载,
|
|
|
+ 用户看到的就是首屏底部那片空白占位,所以这里必须什么都不输出。
|
|
|
+ -->
|
|
|
+ <template v-if="mIndex < renderModuleCount">
|
|
|
<home-top-nav
|
|
|
v-if="mod.key === 'topNav'"
|
|
|
:data="pageData.topNav"
|
|
|
@@ -74,10 +79,12 @@
|
|
|
:hd-id="hdId"
|
|
|
@cart-changed="refreshCartBadgeCount"
|
|
|
/>
|
|
|
+ <!-- 下拉商品后端不截断,用 limit 做前端分页,滚动接近底部再追加 -->
|
|
|
<home-goods-section
|
|
|
v-else-if="mod.key === 'pullGoods'"
|
|
|
:data="pageData.pullGoods"
|
|
|
module-key="pullGoods"
|
|
|
+ :limit="pullGoodsLimit"
|
|
|
:account="account"
|
|
|
:hd-id="hdId"
|
|
|
@cart-changed="refreshCartBadgeCount"
|
|
|
@@ -121,6 +128,17 @@ import { getHdInfo } from '@/api/hd'
|
|
|
import { getInfo } from '@/api/shop'
|
|
|
import { checkArrival } from '@/api/hb'
|
|
|
|
|
|
+/**
|
|
|
+ * 首屏先挂载的模块个数。
|
|
|
+ * 一屏(自定义导航 + 顶部搜索 + 轮播 + 金刚区)已基本占满,多给 1 个内容模块作缓冲。
|
|
|
+ * 这只是起点:真实高度不够时由底部哨兵观察器继续补,直到内容铺满首屏,
|
|
|
+ * 所以这里不需要(也不应该)为了怕留白而调大。
|
|
|
+ */
|
|
|
+const INITIAL_MODULE_COUNT = 4
|
|
|
+
|
|
|
+/** 下拉商品每次追加的条数 */
|
|
|
+const PULL_GOODS_PAGE_SIZE = 10
|
|
|
+
|
|
|
export default {
|
|
|
name: 'shopHomeIndex',
|
|
|
components: {
|
|
|
@@ -149,7 +167,13 @@ export default {
|
|
|
hbArrivalShow: false,
|
|
|
hbArrivalInfo: {},
|
|
|
hbArrivalIds: [],
|
|
|
- hbArrivalLoading: false
|
|
|
+ hbArrivalLoading: false,
|
|
|
+ /** 已挂载的模块个数,随滚动递增,控制首屏节点数与图片请求数 */
|
|
|
+ renderModuleCount: INITIAL_MODULE_COUNT,
|
|
|
+ /** 下拉商品当前渲染条数,滚动触底递增 */
|
|
|
+ pullGoodsLimit: PULL_GOODS_PAGE_SIZE,
|
|
|
+ /** 底部哨兵观察器,驱动模块与下拉商品的按需加载 */
|
|
|
+ _loadMoreObserver: null
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
@@ -158,6 +182,9 @@ export default {
|
|
|
this.shopInfo = cached
|
|
|
}
|
|
|
},
|
|
|
+ onUnload() {
|
|
|
+ this.destroyLoadMoreObserver()
|
|
|
+ },
|
|
|
/** 从分类/购物车返回时刷新角标,避免仍显示旧数量 */
|
|
|
onShow() {
|
|
|
this.syncLoginStyle()
|
|
|
@@ -212,6 +239,23 @@ export default {
|
|
|
modules() {
|
|
|
return (this.pageData && this.pageData.modules) || []
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 已启用的模块,按后台配置顺序排列
|
|
|
+ * 把 enabled 过滤从模板上移到这里,模块下标才能与「已挂载个数」对齐
|
|
|
+ * @returns {Array}
|
|
|
+ */
|
|
|
+ enabledModules() {
|
|
|
+ return this.modules.filter(mod => mod && mod.enabled == 1)
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 下拉商品可展示总数(已剔除售罄),用于判断前端分页是否已到底
|
|
|
+ * 过滤规则需与 goodsSection 的 displayGoods 一致
|
|
|
+ * @returns {number}
|
|
|
+ */
|
|
|
+ pullGoodsTotal() {
|
|
|
+ const list = (this.pageData && this.pageData.pullGoods && this.pageData.pullGoods.goods) || []
|
|
|
+ return list.filter(g => Number(g && g.stock) > 0).length
|
|
|
+ },
|
|
|
showService() {
|
|
|
const topNav = this.pageData && this.pageData.topNav
|
|
|
return !!(topNav && topNav.enabled == 1 && topNav.customerServiceEnabled == 1)
|
|
|
@@ -222,7 +266,12 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
- /** 页面初始化:并行拉取首页配置与门店信息(merchantName) */
|
|
|
+ /**
|
|
|
+ * 页面初始化
|
|
|
+ * 首屏只等 getHome:门店信息(syncMyCustomId)仅供分享参数与红包提醒使用,
|
|
|
+ * 且无 hdId 缓存时是 getHdInfo → getInfo 两跳串行请求,
|
|
|
+ * 放在 Promise.all 里会白白拖慢首屏渲染,因此改为并行且不阻塞 loading。
|
|
|
+ */
|
|
|
init() {
|
|
|
this.loading = true
|
|
|
this.refreshCartBadgeCount()
|
|
|
@@ -230,16 +279,85 @@ export default {
|
|
|
if (account) {
|
|
|
uni.setStorageSync('account', account)
|
|
|
}
|
|
|
- Promise.all([getHome(), this.syncMyCustomId()])
|
|
|
- .then(([homeRes]) => {
|
|
|
+ getHome()
|
|
|
+ .then((homeRes) => {
|
|
|
if (homeRes.code === 1 && homeRes.data) {
|
|
|
+ // 配置可能变化(模块增删),按需渲染的进度需要跟着重置
|
|
|
+ this.renderModuleCount = INITIAL_MODULE_COUNT
|
|
|
+ this.pullGoodsLimit = PULL_GOODS_PAGE_SIZE
|
|
|
this.pageData = homeRes.data
|
|
|
}
|
|
|
- this.checkHbArrival()
|
|
|
})
|
|
|
.finally(() => {
|
|
|
this.loading = false
|
|
|
+ // 必须等 loading 关掉、模块真正上屏后再观察,否则查不到底部哨兵节点
|
|
|
+ this.initLoadMoreObserver()
|
|
|
+ })
|
|
|
+ // 红包提醒依赖 shopInfo.id,必须等门店信息就绪后再查
|
|
|
+ this.syncMyCustomId()
|
|
|
+ .then(() => {
|
|
|
+ this.checkHbArrival()
|
|
|
})
|
|
|
+ .catch(() => {})
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 建立「加载更多」的哨兵观察。
|
|
|
+ *
|
|
|
+ * 页面根节点 .app-content 被全局样式设为 height:100vh + overflow-y:auto,
|
|
|
+ * 滚动发生在这个容器内部而非页面级,因此 onReachBottom / onPageScroll 都不会触发,
|
|
|
+ * 只能靠观察底部节点是否接近可视区来驱动(复用 home-bottom-space,它本身有高度可被观察)。
|
|
|
+ *
|
|
|
+ * 参照区用视口而非 .app-content:视口对「容器滚动」和「页面滚动」两种情况都成立,
|
|
|
+ * 而以 .app-content 为参照时,若哪天改成页面级滚动,容器与节点会一起移动、相对位置不变,
|
|
|
+ * 观察器将永远不回调。
|
|
|
+ */
|
|
|
+ initLoadMoreObserver() {
|
|
|
+ this.destroyLoadMoreObserver()
|
|
|
+ // 观察器不可用时全部渲染:宁可牺牲按需加载,也不能把店铺内容永久藏起来
|
|
|
+ if (!uni.createIntersectionObserver) {
|
|
|
+ this.renderModuleCount = this.enabledModules.length
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this._loadMoreObserver = uni.createIntersectionObserver(this, { thresholds: [0] })
|
|
|
+ // 提前 300px 触发,滚到底之前就把下一块内容准备好
|
|
|
+ this._loadMoreObserver
|
|
|
+ .relativeToViewport({ bottom: 300 })
|
|
|
+ .observe('.home-bottom-space', (res) => {
|
|
|
+ if (res.intersectionRatio > 0) {
|
|
|
+ this.loadMore()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ destroyLoadMoreObserver() {
|
|
|
+ if (this._loadMoreObserver) {
|
|
|
+ this._loadMoreObserver.disconnect()
|
|
|
+ this._loadMoreObserver = null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 挂载下一个模块,模块全部就位后再翻下拉商品。
|
|
|
+ *
|
|
|
+ * 每次真正新增了内容就重建观察器:重新 observe 会立刻回调一次当前相交状态,
|
|
|
+ * 于是这里会自动循环到「内容高度超过一屏 + 预留距离」才停下。
|
|
|
+ * 这同时解决两件事:
|
|
|
+ * 1. 首屏铺满——起始模块数不够时持续补,不会在首屏底部留白;
|
|
|
+ * 2. 空模块不卡死——新挂载的模块可能渲染为空(专区不在活动期、商品全部售罄),
|
|
|
+ * 高度没增长时继续往下补,而不是停在哨兵一直可见却收不到新回调的状态。
|
|
|
+ */
|
|
|
+ loadMore() {
|
|
|
+ let grew = false
|
|
|
+ if (this.renderModuleCount < this.enabledModules.length) {
|
|
|
+ this.renderModuleCount += 1
|
|
|
+ grew = true
|
|
|
+ } else if (this.pullGoodsLimit < this.pullGoodsTotal) {
|
|
|
+ this.pullGoodsLimit += PULL_GOODS_PAGE_SIZE
|
|
|
+ grew = true
|
|
|
+ }
|
|
|
+ if (grew) {
|
|
|
+ this.initLoadMoreObserver()
|
|
|
+ }
|
|
|
},
|
|
|
/** 按登录态驱动 wangCg:0 未登录弹层 / 1 已登录关闭 */
|
|
|
syncLoginStyle() {
|