Jacob
1 天以前 f7034f535b85264e0c5b18e82fc295dab2052304
pages/goods_cate/goods_cate.vue
....@@ -13,7 +13,7 @@
1313 <scroll-view scroll-y="true" scroll-with-animation='true'
1414 :style="{height: (height-280/750*windowWidth) + 'px'}" :scroll-into-view="'menu_item'+navActive">
1515 <view class='item acea-row row-center-wrapper menu_item' :id="'menu_item'+index"
16
- :class='index==navActive?"on":""' v-for="(item,index) in categoryList" :key="item._key"
16
+ :class='index==navActive?"on":""' v-for="(item,index) in categoryList" :key="index"
1717 @click='tap(index,"b"+index)'>
1818 <image v-if="item.extra" class="img" :src="item.extra" mode="aspectFill"></image>
1919 <view class="text">{{item.name}}</view>
....@@ -25,7 +25,7 @@
2525 <scroll-view :scroll-into-view="'subMenu_item'+subActive" scroll-x="true" scroll-with-animation='true'
2626 style="width: 100%;">
2727 <view class="subMenu_wrapper">
28
- <block v-for="(itemn,indexn) in categoryList[navActive].child" :key="itemn._key"><text
28
+ <block v-for="(itemn,indexn) in categoryList[navActive].child" :key="indexn"><text
2929 class="subMenu_item" :id="'subMenu_item'+indexn" :class='indexn==subActive?"on":""'
3030 @click='tapSub(indexn)'>{{itemn.name}}</text></block>
3131 </view>
....@@ -35,15 +35,15 @@
3535 :style="{top: (categoryList[navActive].show2Category==0?0:118) + 'rpx',height: (height-(categoryList[navActive].show2Category==0?0:(122/750*windowWidth))) + 'px'}">
3636 <scroll-view :scroll-top="scrollTop" class="goodlistScroll" scroll-y="true" style="height:100%;"
3737 :scroll-into-view="scrollIntoView" @scroll="handleScroll" :throttle="false">
38
- <block v-for="(aitem,aindex) in categoryList" :key="aitem._key">
39
- <view class="list" v-if="aitem.child.length>0" v-for="(sitem,sindex) in aitem.child" :key="sitem._key"
38
+ <block v-for="(aitem,aindex) in categoryList" :key="aindex">
39
+ <view class="list" v-if="aitem.child.length>0" v-for="sitem,sindex in aitem.child" :key="sindex"
4040 :style="(aindex==categoryList.length-1&&sindex==aitem.child.length-1)?('min-height:'+(height-370/750*windowWidth)+'px'):''"
4141 :id="'list'+aindex+'_'+sindex">
4242 <view class="list_title">{{sitem.name}}</view>
4343 <view class="list_wrapper cf">
4444 <view class="list_column cfl">
4545 <block style="float:left" v-if="sitem.productArr.length>0"
46
- v-for="(gitem,gindex) in sitem.productArr" :key="gitem._key">
46
+ v-for="(gitem,gindex) in sitem.productArr" :key="gindex">
4747 <GoodBlock v-if="gindex%2==0" :size="'small'" :pageName="'分类'"
4848 :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem">
4949 </GoodBlock>
....@@ -51,7 +51,7 @@
5151 </view>
5252 <view class="list_column cfr">
5353 <block style="float:left" v-if="sitem.productArr.length>0"
54
- v-for="(gitem,gindex) in sitem.productArr" :key="gitem._key">
54
+ v-for="(gitem,gindex) in sitem.productArr" :key="gindex">
5555 <GoodBlock v-if="gindex%2==1" :size="'small'" :pageName="'分类'"
5656 :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem">
5757 </GoodBlock>
....@@ -66,14 +66,14 @@
6666 <view class="list_wrapper cf">
6767 <view class="list_column cfl">
6868 <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr"
69
- :key="gitem._key">
69
+ :key="gindex">
7070 <GoodBlock v-if="gindex%2==0" :pageName="'分类'" :moduleName="'分类商品列表'"
7171 :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock>
7272 </block>
7373 </view>
7474 <view class="list_column cfr">
7575 <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr"
76
- :key="gitem._key">
76
+ :key="gindex">
7777 <GoodBlock v-if="gindex%2==1" :pageName="'分类'" :moduleName="'分类商品列表'"
7878 :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock>
7979 </block>
....@@ -144,7 +144,11 @@
144144 totalGetNum: 0,
145145 // 首屏优化:当前分类的待完成请求数
146146 catePending: [],
147
+ // 标记各分类是否已派发过请求,避免切换分类时重复请求
148
+ cateStarted: [],
147149 firstScreenReady: true,
150
+ // 后台加载标记:首屏渲染并关闭loading后,才开始后台加载其余分类
151
+ bgLoaded: false,
148152 // 渲染优化:非首屏分类数据缓冲队列,批量渲染减少setData次数
149153 deferredLists: [],
150154 deferTimer: null,
....@@ -162,6 +166,8 @@
162166 let that = this;
163167 that.menuTop = uni.getMenuButtonBoundingClientRect().top;
164168 that.menuHeight = uni.getMenuButtonBoundingClientRect().height;
169
+ // 尽早计算页面布局高度,避免首屏数据就绪后因 height 为 0 而白屏
170
+ that.infoScroll();
165171 if (that.$store.getters.careBeanRate) {
166172 that.$nextTick(() => {
167173 that.$set(that, 'careBeanRate', that.$store.getters.careBeanRate)
....@@ -176,6 +182,10 @@
176182 });
177183 },
178184 mounted() {},
185
+ onReady() {
186
+ // 页面渲染完成后复核一次布局尺寸(含滚动区域 scrollHeight)
187
+ this.infoScroll();
188
+ },
179189 onHide() {
180190 let that = this;
181191 if (that.$store.getters.isSystemHide) {
....@@ -339,6 +349,10 @@
339349 that.isClicking = true;
340350 that.navActive = index;
341351 that.subActive = 0;
352
+ // loading期间切到尚未发起请求的分类时,立即补发请求,避免loading卡住
353
+ if (!that.cateStarted[index]) {
354
+ that.loadCateByIndex(index);
355
+ }
342356 // loading期间切换分类,若目标分类已加载完成则立即关闭loading
343357 that.checkFirstScreen(index);
344358 that.$nextTick(() => {
....@@ -352,6 +366,10 @@
352366 let that = this;
353367 that.isClicking = true;
354368 that.subActive = index;
369
+ // 当前分类尚未发起请求时补发,避免loading卡住
370
+ if (!that.cateStarted[that.navActive]) {
371
+ that.loadCateByIndex(that.navActive);
372
+ }
355373 that.checkFirstScreen(that.navActive);
356374 that.$nextTick(() => {
357375 that.scrollIntoView = 'list' + that.navActive + '_' + that.subActive;
....@@ -391,23 +409,28 @@
391409 let that = this;
392410 that.firstScreenReady = false;
393411 that.allRequestsDone = false;
412
+ that.bgLoaded = false;
394413 that.deferredLists = [];
395414 if (that.deferTimer) {
396415 clearTimeout(that.deferTimer);
397416 that.deferTimer = null;
398417 }
418
+ // 暂不考虑传入cid的情况:一律从第一个分类开始加载首屏
419
+ that.navActive = 0;
420
+ that.subActive = 0;
399421 uni.showLoading();
400422 getCategoryList().then(res => {
401
- that.categoryList = res.data;
402
- // [P0-优化] 装配阶段为所有分类节点打 _key,供模板 :key 使用
403
- that.ensureCategoryKey(that.categoryList);
423
+ that.categoryList = res.data || [];
404424 // 重置计数,避免重复进入页面时累积
405425 that.totalGetNum = 0;
406426 that.finishNum = 0;
407427 that.catePending = [];
428
+ that.cateStarted = [];
408429 that.categoryList.forEach((item, index) => {
409
-
410430 if (item.child && item.child.length > 0) {
431
+ item.child.sort((a, b) => {
432
+ return a.sort - b.sort;
433
+ });
411434 that.totalGetNum += item.child.length;
412435 that.catePending[index] = item.child.length;
413436 } else {
....@@ -415,113 +438,50 @@
415438 that.catePending[index] = 1;
416439 }
417440 })
418
-
419
- // 先标记当前选中分类,再发起请求
420
- that.categoryList.forEach((item, index) => {
421
- if (that.optionId && that.optionId == item.id) {
422
- that.navActive = index;
423
- that.subActive = 0;
424
- }
425
- if (item.child && item.child.length > 0) {
426
- item.child.sort((a, b) => {
427
- return a.sort - b.sort;
428
- });
429
- item.child.forEach((item2, index2) => {
430
- if (that.optionId && that.optionId == item2.id) {
431
- that.navActive = index;
432
- that.subActive = index2;
433
- }
434
- })
435
- }
441
+ // 分类为空时直接关闭loading,避免一直停留在loading态
442
+ if (!that.categoryList.length) {
443
+ that.firstScreenReady = true;
444
+ that.isLoading = false;
445
+ uni.hideLoading();
446
+ return;
447
+ }
448
+ // 首屏优化:先只请求第一个分类,保证首屏数据最快返回并直接渲染
449
+ that.loadCateByIndex(that.navActive);
450
+ // 其余分类待首屏渲染、loading关闭后,由 loadRestCategory 后台错峰加载
451
+ })
452
+ },
453
+ // 按索引加载某个分类下的商品数据(同一分类只派发一次)
454
+ loadCateByIndex(index) {
455
+ let that = this;
456
+ let item = that.categoryList[index];
457
+ if (!item) return;
458
+ if (that.cateStarted[index]) return;
459
+ that.cateStarted[index] = true;
460
+ if (item.child && item.child.length > 0) {
461
+ item.child.forEach((item2, index2) => {
462
+ that.getAllProductList('isSub', index, index2);
436463 })
437
-
438
- const loadCate = (index) => {
439
- let item = that.categoryList[index];
440
- if (item.child && item.child.length > 0) {
441
- item.child.forEach((item2, index2) => {
442
- that.getAllProductList('isSub', index, index2);
443
- })
444
- } else {
445
- that.getAllProductList(false, index);
446
- }
447
- };
448
- // [P0-优化] 首屏分类(当前navActive):立即并发发起,不走任何队列,最大限度缩短首屏 loading 时长
449
- loadCate(that.navActive);
450
- // [P0-优化] 其余分类:构建任务列表,走下方并发限流池,避免一次性占满 wx.request queue 拖慢首屏
451
- let otherTasks = [];
452
- that.categoryList.forEach((it, idx) => {
453
- if (idx !== that.navActive) otherTasks.push({ index: idx });
454
- });
455
- that.runCategoryTasksWithLimit(otherTasks, 4);
456
-
457
-
458
- })
459
- },
460
- // [P0-优化] 并发限流工具:除首屏分类外,其余分类的请求经由此处发起
461
- runCategoryTasksWithLimit(tasks, limit) {
462
- let that = this;
463
- if (!tasks || !tasks.length) return Promise.resolve();
464
- const queue = tasks.slice();
465
- const executing = [];
466
- const launch = () => {
467
- while (queue.length && executing.length < limit) {
468
- const task = queue.shift();
469
- const item = that.categoryList[task.index];
470
- if (!item) continue;
471
- let promise;
472
- if (item.child && item.child.length > 0) {
473
- const subPromises = [];
474
- item.child.forEach((sub, subIdx) => {
475
- subPromises.push(that.getAllProductList('isSub', task.index, subIdx));
476
- });
477
- promise = Promise.all(subPromises);
478
- } else {
479
- promise = that.getAllProductList(false, task.index);
480
- }
481
- // 用 .catch 包一层避免单个失败拖垮整池
482
- const tracked = promise.catch(() => {});
483
- tracked.finally(() => {
484
- const idx = executing.indexOf(tracked);
485
- if (idx >= 0) executing.splice(idx, 1);
486
- }).catch(() => {});
487
- executing.push(tracked);
488
- }
489
- };
490
- const next = () => {
491
- launch();
492
- if (executing.length === 0) return Promise.resolve();
493
- return Promise.race(executing).then(next, next);
494
- };
495
- return next();
496
- },
497
- // [P0-优化] 为分类节点打 _key:非 H5 平台的 v-for :key 必须为简单变量,
498
- // 故在数据装配阶段预先在 item 上加 _key,模板用 :key="item._key" 引用
499
- ensureCategoryKey: function(list) {
500
- if (!Array.isArray(list)) return;
501
- list.forEach((item, i) => {
502
- if (!item) return;
503
- if (item.id != null) item._key = 'c-' + item.id;
504
- else if (!item._key) item._key = 'c0-' + i;
505
- if (item.child && item.child.length) {
506
- item.child.forEach((sub, j) => {
507
- if (!sub) return;
508
- if (sub.id != null) sub._key = 's-' + sub.id;
509
- else if (!sub._key) sub._key = 's0-' + i + '-' + j;
510
- });
511
- }
512
- });
513
- },
514
- // [P0-优化] 为商品节点打 _key
515
- ensureProductKey: function(list) {
516
- if (!Array.isArray(list)) return;
517
- list.forEach((g, k) => {
518
- if (!g) return;
519
- if (g.id != null && !g._key) g._key = 'p-' + g.id;
520
- else if (!g._key) g._key = 'pk-' + k;
521
- });
522
- },
523
- // 当前分类数据全部就绪后立即关闭全屏loading,不必等待所有分类
524
- checkFirstScreen(index1) {
464
+ } else {
465
+ that.getAllProductList(false, index);
466
+ }
467
+ },
468
+ // 首屏渲染并关闭loading后,后台错峰加载其余分类,避免与首屏抢请求/抢渲染
469
+ loadRestCategory() {
470
+ let that = this;
471
+ if (that.bgLoaded) return;
472
+ that.bgLoaded = true;
473
+ let delay = 0;
474
+ that.categoryList.forEach((item, index) => {
475
+ if (index == that.navActive) return;
476
+ // 每个分类错开触发,逐步在后台铺开,避免一次性铺满请求队列
477
+ delay += 50;
478
+ setTimeout(() => {
479
+ that.loadCateByIndex(index);
480
+ }, delay);
481
+ })
482
+ },
483
+ // 当前分类数据全部就绪后立即关闭全屏loading,不必等待所有分类
484
+ checkFirstScreen(index1) {
525485 let that = this;
526486 if (that.firstScreenReady || index1 != that.navActive) return;
527487 if ((that.catePending[index1] || 0) <= 0) {
....@@ -531,6 +491,8 @@
531491 that.$nextTick(() => {
532492 setTimeout(() => {
533493 uni.hideLoading();
494
+ // 首屏已渲染且loading已关闭,此时才开始后台加载其余分类
495
+ that.loadRestCategory();
534496 }, 50)
535497 })
536498 }
....@@ -600,7 +562,8 @@
600562 updateDimensions() {
601563 const query = uni.createSelectorQuery().in(this);
602564 query.select('.goodlistScroll').boundingClientRect(data => {
603
- this.scrollHeight = data.height;
565
+ // 首屏渲染前该节点可能还不存在,避免报错
566
+ if (data) this.scrollHeight = data.height;
604567 }).exec();
605568 },
606569 handleScroll(e) {
....@@ -631,8 +594,6 @@
631594 cid: isSub ? that.categoryList[index1].child[index2].id : that.categoryList[index1].id,
632595 }).then(res => {
633596 let list = res.data.list;
634
- // [P0-优化] 装配阶段为商品节点打 _key,供模板 :key 使用
635
- that.ensureProductKey(list);
636597 // 当前浏览分类即时渲染;其余分类进入缓冲队列批量渲染,
637598 // 避免几十次$set各自触发一次全量列表渲染
638599 if (index1 == that.navActive && !that.firstScreenReady) {