| .. | .. |
|---|
| 13 | 13 | <scroll-view scroll-y="true" scroll-with-animation='true' |
|---|
| 14 | 14 | :style="{height: (height-280/750*windowWidth) + 'px'}" :scroll-into-view="'menu_item'+navActive"> |
|---|
| 15 | 15 | <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" |
|---|
| 17 | 17 | @click='tap(index,"b"+index)'> |
|---|
| 18 | 18 | <image v-if="item.extra" class="img" :src="item.extra" mode="aspectFill"></image> |
|---|
| 19 | 19 | <view class="text">{{item.name}}</view> |
|---|
| .. | .. |
|---|
| 25 | 25 | <scroll-view :scroll-into-view="'subMenu_item'+subActive" scroll-x="true" scroll-with-animation='true' |
|---|
| 26 | 26 | style="width: 100%;"> |
|---|
| 27 | 27 | <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 |
|---|
| 29 | 29 | class="subMenu_item" :id="'subMenu_item'+indexn" :class='indexn==subActive?"on":""' |
|---|
| 30 | 30 | @click='tapSub(indexn)'>{{itemn.name}}</text></block> |
|---|
| 31 | 31 | </view> |
|---|
| .. | .. |
|---|
| 35 | 35 | :style="{top: (categoryList[navActive].show2Category==0?0:118) + 'rpx',height: (height-(categoryList[navActive].show2Category==0?0:(122/750*windowWidth))) + 'px'}"> |
|---|
| 36 | 36 | <scroll-view :scroll-top="scrollTop" class="goodlistScroll" scroll-y="true" style="height:100%;" |
|---|
| 37 | 37 | :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" |
|---|
| 40 | 40 | :style="(aindex==categoryList.length-1&&sindex==aitem.child.length-1)?('min-height:'+(height-370/750*windowWidth)+'px'):''" |
|---|
| 41 | 41 | :id="'list'+aindex+'_'+sindex"> |
|---|
| 42 | 42 | <view class="list_title">{{sitem.name}}</view> |
|---|
| 43 | 43 | <view class="list_wrapper cf"> |
|---|
| 44 | 44 | <view class="list_column cfl"> |
|---|
| 45 | 45 | <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"> |
|---|
| 47 | 47 | <GoodBlock v-if="gindex%2==0" :size="'small'" :pageName="'分类'" |
|---|
| 48 | 48 | :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem"> |
|---|
| 49 | 49 | </GoodBlock> |
|---|
| .. | .. |
|---|
| 51 | 51 | </view> |
|---|
| 52 | 52 | <view class="list_column cfr"> |
|---|
| 53 | 53 | <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"> |
|---|
| 55 | 55 | <GoodBlock v-if="gindex%2==1" :size="'small'" :pageName="'分类'" |
|---|
| 56 | 56 | :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem"> |
|---|
| 57 | 57 | </GoodBlock> |
|---|
| .. | .. |
|---|
| 66 | 66 | <view class="list_wrapper cf"> |
|---|
| 67 | 67 | <view class="list_column cfl"> |
|---|
| 68 | 68 | <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr" |
|---|
| 69 | | - :key="gitem._key"> |
|---|
| 69 | + :key="gindex"> |
|---|
| 70 | 70 | <GoodBlock v-if="gindex%2==0" :pageName="'分类'" :moduleName="'分类商品列表'" |
|---|
| 71 | 71 | :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock> |
|---|
| 72 | 72 | </block> |
|---|
| 73 | 73 | </view> |
|---|
| 74 | 74 | <view class="list_column cfr"> |
|---|
| 75 | 75 | <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr" |
|---|
| 76 | | - :key="gitem._key"> |
|---|
| 76 | + :key="gindex"> |
|---|
| 77 | 77 | <GoodBlock v-if="gindex%2==1" :pageName="'分类'" :moduleName="'分类商品列表'" |
|---|
| 78 | 78 | :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock> |
|---|
| 79 | 79 | </block> |
|---|
| .. | .. |
|---|
| 399 | 399 | uni.showLoading(); |
|---|
| 400 | 400 | getCategoryList().then(res => { |
|---|
| 401 | 401 | that.categoryList = res.data; |
|---|
| 402 | | - // [P0-优化] 装配阶段为所有分类节点打 _key,供模板 :key 使用 |
|---|
| 403 | | - that.ensureCategoryKey(that.categoryList); |
|---|
| 404 | 402 | // 重置计数,避免重复进入页面时累积 |
|---|
| 405 | 403 | that.totalGetNum = 0; |
|---|
| 406 | 404 | that.finishNum = 0; |
|---|
| .. | .. |
|---|
| 445 | 443 | that.getAllProductList(false, index); |
|---|
| 446 | 444 | } |
|---|
| 447 | 445 | }; |
|---|
| 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); |
|---|
| 446 | + // 当前分类优先请求,保证首屏数据最先返回 |
|---|
| 447 | + loadCate(that.navActive); |
|---|
| 448 | + that.categoryList.forEach((item, index) => { |
|---|
| 449 | + if (index != that.navActive) { |
|---|
| 450 | + loadCate(index); |
|---|
| 451 | + } |
|---|
| 452 | + }) |
|---|
| 456 | 453 | |
|---|
| 457 | 454 | |
|---|
| 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) { |
|---|
| 455 | + }) |
|---|
| 456 | + }, |
|---|
| 457 | + // 当前分类数据全部就绪后立即关闭全屏loading,不必等待所有分类 |
|---|
| 458 | + checkFirstScreen(index1) { |
|---|
| 525 | 459 | let that = this; |
|---|
| 526 | 460 | if (that.firstScreenReady || index1 != that.navActive) return; |
|---|
| 527 | 461 | if ((that.catePending[index1] || 0) <= 0) { |
|---|
| .. | .. |
|---|
| 631 | 565 | cid: isSub ? that.categoryList[index1].child[index2].id : that.categoryList[index1].id, |
|---|
| 632 | 566 | }).then(res => { |
|---|
| 633 | 567 | let list = res.data.list; |
|---|
| 634 | | - // [P0-优化] 装配阶段为商品节点打 _key,供模板 :key 使用 |
|---|
| 635 | | - that.ensureProductKey(list); |
|---|
| 636 | 568 | // 当前浏览分类即时渲染;其余分类进入缓冲队列批量渲染, |
|---|
| 637 | 569 | // 避免几十次$set各自触发一次全量列表渲染 |
|---|
| 638 | 570 | if (index1 == that.navActive && !that.firstScreenReady) { |
|---|