ArkUI点单页,二维列表及懒加载的两种写法

项目需要制作一个如下图的点单页面,基于API12写了基于二维列表和IDataSource的两种写法

一、二维列表

        二维列表的思路较为简单,其中的一维索引指定了侧边栏的索引,二维索引为具体的商品数据,侧边栏通过List实现,并通过scrollToIndex控制scroll控制器控制商品栏跳转到指定目录下,右侧则通过onScrollIndex检查结束位置的索引获取服务器上的数据按需获取减轻服务器压力。切换顶部tabs会清空itemList,但是这种实现方式在数据较多的时候内存占用较大。最初尝试过三维列表存储顶部、侧边导航栏具体数据,但在模拟器中@State装饰器似乎不能检测到第三维的数据更改,所以放弃三维列表存储页面的数据。

        具体实现如下,未引用额外的文件。

import { Api, ApiUrls, Data, setTitle } from '@ohos/common';
import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
export struct OrderPage {
  // 商品分类信息
  @State productCate: Data[] = [];
  // 店铺列表
  @State shopList: Data[] = [];
  // 顶部栏Index
  @State currentIndex: number = 0;
  // 侧边栏Index,索引代表顶部Index
  @State sCurrentIndex: number[] = [];
  // 商品列表,二维数组,一维索引代表侧边Index,其中Array代表具体数据
  @State itemList: Data[][] = [];
  // 控制侧边栏隐显
  @State showRightSide: boolean = false;
  // 侧栏滚动控制器
  private listScroller: Scroller = new Scroller();

  aboutToAppear(): void {
    this.fetchShopList();
    this.fetchProductCate().then(() => {
      // 调取数据后执行,将侧边栏Index根据顶部数量置空
      this.sCurrentIndex = new Array(this.productCate.length).fill(0);

      // 初始化当前tab下itemList
      this.itemList = this.initializeItemList((this.productCate[this.currentIndex].effect_cate_list as Data[]).length);
      this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex]);
    });
  }

  @Builder
  TabBuilder(title_cn: string, title_en: string, targetIndex: number, isLast: boolean) {
    Column() {
      Text(title_cn)
        .fontColor(this.currentIndex === targetIndex ? '#333333' : '#999999')
        .fontSize(14)
      Text(title_en)
        .fontColor(this.currentIndex === targetIndex ? '#333333' : '#999999')
        .fontSize(12)
    }
    .border({
      width: {
        right: !isLast ? 1 : 0
      },
      color: '#d1cac1'
    })
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  // targetIndex: 侧边Index, Index: 顶部index
  sTabBuilder(title: string, targetIndex: number, index: number) {
    Column() {
      Text(title)
        .fontColor(this.sCurrentIndex[index] === targetIndex ? '#ff6827' : '#666056')
        .fontSize(14)
    }
    .onClick(() => {
      this.sCurrentIndex[index] = targetIndex;
      if (!this.itemList[targetIndex].length) {
        this.fetchProductList(index, targetIndex)
      }
      this.listScroller.scrollToIndex(targetIndex)
    })
    .height(45)
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor(this.sCurrentIndex[this.currentIndex] === targetIndex ? Color.White : '#f5f5f5')
  }

  @Builder
  ListGroupHeader(title: string) {
    Text(title)
      .fontSize(12)
      .fontColor('#666056')
      .margin({
        top: 15,
        left: 11
      })
  }

  build() {
    NavDestination() {
      Flex({ direction: FlexDirection.Column, space: { main: LengthMetrics.vp(10) } }) {
        // 顶部图片+文字
        Column() {
          Image($rawfile('OrderPage/background.png'))
            .position({
              x: 0,
              y: 0
            })
            .width('100%')
          Row() {
            Column() {
              Text('汤头泡泡')
                .fontSize(20)
                .fontColor(Color.White)
              Row({ space: 5 }) {
                Text(this.shopList.length ? this.shopList[0].title as string : '')
                  .fontSize(14)
                  .fontColor(Color.White)
                // Polyline({ width: 8, height: 12 })
                //   .points([[0, 0], [7, 5.5], [0, 11]])
                //   .fillOpacity(0)
                //   .stroke(Color.White)
                //   .strokeWidth(2)
              }
            }
            .alignItems(HorizontalAlign.Start)

            Button('门店自取', { type: ButtonType.Normal })
              .borderRadius(3)
              .fontSize(14)
              .fontColor(Color.White)
              .backgroundColor('#ff6827')
          }
          .height('100%')
          .width('100%')
          .padding(15)
          .justifyContent(FlexAlign.SpaceBetween)
          .alignItems(VerticalAlign.Center)
          .backgroundColor('#4c000000')
        }
        .height(132)

        // 顶tabs栏
        Tabs() {
          ForEach(this.productCate, (item: Data, index: number) => {
            TabContent() {
              if (this.currentIndex === index) {
                Flex({ direction: FlexDirection.Column }) {
                  // 商品列表
                  Flex() {
                    List({ initialIndex: this.sCurrentIndex[index] }) {
                      ForEach(item.effect_cate_list as Data[], (sItem: Data, sIndex: number) => {
                        this.sTabBuilder(sItem.title as string, sIndex, index)
                      }, () => index.toString())
                    }
                    .height('100%')
                    .scrollBar(BarState.Off)
                    .flexBasis(88)

                    List({ scroller: this.listScroller }) {
                      // 在这里通过index跳转看看能不能跳转到相应的项
                      if (this.showRightSide) {
                        ForEach(this.itemList, (sItem: Data[], sIndex: number) => {
                          ListItemGroup({
                            header: this.ListGroupHeader((this.productCate[index].effect_cate_list as Data[])[sIndex].title as string)
                          }) {
                            // rItem为右侧数据项
                            ForEach(sItem, (rItem: Data, rIndex: number) => {
                              ListItem() {
                                productItem({ item: rItem, isTop: this.itemList[sIndex][0] === rItem })
                              }
                            })
                          }
                        })
                      }
                    }
                    .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
                      this.sCurrentIndex[this.currentIndex] = lastIndex;
                      if (!this.itemList[lastIndex].length) {
                        this.fetchProductList(this.currentIndex, lastIndex)
                      }
                    })
                    .flexGrow(1)
                    .sticky(StickyStyle.Header)
                    .height('100%')
                    .backgroundColor(Color.White)
                  }

                  // 底部购物车
                  Flex() {

                  }
                  .height(49)
                  .width('100%')
                  .backgroundColor(Color.Blue)
                }
              }
            }
            .tabBar(this.TabBuilder(item.title as string, item.sub_title as string, index,
              index === this.productCate.length - 1 ? true : false))
          }, (item: Data) => item.title as string)
        }
        .scrollable(false)
        .onChange((index) => {
          this.currentIndex = index;
          // 重新生成itemList
          this.showRightSide = false;
          this.itemList = this.initializeItemList((this.productCate[index].effect_cate_list as Data[]).length);
          this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex]);
        })
        .divider({
          strokeWidth: 10,
          color: '#f5f5f5'
        })
        .barHeight(50)
        .flexGrow(1)
        .backgroundColor(Color.White)
      }
      .backgroundColor('#f5f5f5')
    }
    .title(setTitle('门店点单'))
  }

  initializeItemList(length: number) {
    return new Array(length).fill(null).map(() => []);
  };

  async fetchShopList() {
    const res = await Api("GET", ApiUrls.shopList, {}, false)
    this.shopList = (res.results as Data).data as Data[];
  }

  async fetchProductCate() {
    const res = await Api("GET", ApiUrls.productCate, {}, false)
    this.productCate = (res.results as Data).data as Data[];
  }

  // 接受顶部和侧边栏的index,从productCate中读取数据
  async fetchProductList(topIndex: number, sideIndex: number) {
    const params: Data = {
      'cate_id': this.productCate[topIndex].id?.toString(),
      'effect_cate_id': (this.productCate[topIndex].effect_cate_list as Data[])[sideIndex].id?.toString()
    };
    const res = await Api("GET", ApiUrls.shopProductList, params, false);
    // 拼接新获取的数据到现有的列表中
    this.itemList[sideIndex] = (res.results as Data).data as Data[];
    if (!this.showRightSide) {
      this.showRightSide = true;
    }
  }
}

@Component
struct productItem {
  @Prop item: Data;
  @Prop isTop: boolean;

  build() {
    Flex({ direction: FlexDirection.Row }) {
      Image((this.item.cover as Data[])[0].source as string)
        .flexBasis(80)
        .height(80)
      Column() {
        Text(this.item.title as string)
          .fontSize(14)
          .fontColor('#333333')
        if ((this.item.posts_list as Data[]).length > 0) {
          Text(`本膳食依据${(this.item.posts_list as Data[])[0].reference as string}所记载的${(this.item.posts_list as Data[])[0].reference_title as string},采用西点工艺制成。`)
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .fontSize(11)
            .fontColor('#666056')
            .margin({
              top: 2
            })
            .width(165)
        }
        Row() {
          Text() {
            Span('¥')
              .fontSize(12)
            Span(this.item.sku_min_price as string)
              .fontSize(17)
          }
          .fontColor('#333333')

          Button('选规格')
            .fontSize(13)
            .height(21)
            .backgroundColor('#ff6827')
        }
        .width(165)
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .height(80)
      .justifyContent(FlexAlign.SpaceBetween)
      .alignItems(HorizontalAlign.Start)
      .flexGrow(1)
      .padding({
        left: 10
      })
    }
    .padding({
      top: this.isTop ? 11 : 16,
      bottom: 15,
      left: 10
    })
    .border({
      width: {
        bottom: 1
      },
      color: '#eeeeee'
    })
  }
}

二、懒加载的实现

        ArkTS包含懒加载的渲染模式,基于懒加载对二维数组做了一定的优化,注意forEach中嵌套lazyforeach这种做法会导致懒加载功能失效(询问工程师得到的)。当然通过if渲染切换页签是不算的。官网上是用string做的例子,这里尝试用二维数组实现。

        首先是type.ets文件,这里我踩了几个坑(不仔细看官方文档的后果),在IDataSource 里面,totalCount的结果是懒加载会渲染的总条目数,getData是循环时得到的具体项,这个地方不要修改,如果有需要得到数据的需求可以自己重新写几个public方法,我这里写了totalChildCount和getChildData。

type Data = Record<string, string | boolean | number | undefined | object | object[]>;

type ApiEndpoints = Record<string, string>;

class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: Data[] | string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): Data | string | Data[] {
    return this.originDataArray[index];
  }

  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  // 通知LazyForEach组件需要重载所有子组件
  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  // 通知LazyForEach组件需要在index对应索引处添加子组件
  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  // 通知LazyForEach组件需要在index对应索引处删除该子组件
  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}
@Observed class ProductInfoSource extends BasicDataSource {
  private dataArray: Data[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): Data {
    return this.dataArray[index];
  }

  public addData(index: number, data: Data): void {
    this.dataArray.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  public pushData(data: Data): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public pushDataList(data: Data[]): void {
    // 添加新数据
    const startIndex = this.dataArray.length;
    this.dataArray = [...this.dataArray, ...data];
    // 通知每个新增数据项
    for (let i = 0; i < data.length; i++) {
      this.notifyDataAdd(startIndex + i);
    }
  }

  public clearDataList(): void {
    this.dataArray = [];
  }
}
@Observed class ProductInfoSourceArray extends BasicDataSource {
  private dataArray: Data[][] = [];
  // 操作前先更新firstIndex
  private firstIndex: number = 0;

  // 懒加载渲染数目基于totalcount
  public totalCount(): number {
    return this.dataArray.length;
  }

  public childCount(): number {
    return this.dataArray[this.firstIndex].length;
  }
  // 防数组越界在调用时实现
  public nextChildCount(): number {
    return this.dataArray[this.firstIndex + 1].length;
  }

  public preChildCount(): number {
    return this.dataArray[this.firstIndex - 1].length;
  }

  // 懒加载渲染数据基于getData
  public getData(index: number): Data[] {
    return this.dataArray[index];
  }

  public getChildData(index: number): Data {
    return this.dataArray[this.firstIndex][index];
  }

  public updateIndex(index: number): void {
    this.firstIndex = index;
  }

  public addData(index: number, data: Data): void {
    this.dataArray[this.firstIndex].splice(index, 0, data);
    this.notifyDataChange(index);
  }

  public pushData(data: Data): void {
    this.dataArray[this.firstIndex].push(data);
    this.notifyDataChange(this.firstIndex);
  }

  public refreshDataList(data: Data[]): void {
    // 添加新数据
    this.dataArray[this.firstIndex] = data;
    console.log(JSON.stringify(this.dataArray[this.firstIndex]))
    // 通知修改项的下标
    this.notifyDataChange(this.firstIndex);
  }

  public initialize(length: number): void {
    this.dataArray = new Array(length).fill(null).map(() => []);
    for (let index = 0; index < length; index++) {
      this.notifyDataAdd(index);
    }
  }

  public clearDataList(): void {
    this.dataArray = [];
  }
}
export {
  Data,
  ApiEndpoints,
  ProductInfoSource,
  ProductInfoSourceArray
}

        然后这里是基于懒加载的渲染方法,OrderPage.ets,后续优化内容我就不更新了

import { Api, ApiUrls, Data, ProductInfoSourceArray, setTitle } from '@ohos/common';
import { LengthMetrics } from '@kit.ArkUI';

@Entry
@Component
export struct OrderPage {
  // 商品分类信息
  @State productCate: Data[] = [];
  // 店铺列表
  @State shopList: Data[] = [];
  // 顶部栏Index
  @State currentIndex: number = 0;
  // 侧边栏Index,索引代表顶部Index
  @State sCurrentIndex: number[] = [];
  // 商品列表,二维数组,一维索引代表侧边Index,其中Array代表具体数据
  @State itemList: Data[][] = [];
  // 懒加载列表
  @State lazyItemList: ProductInfoSourceArray = new ProductInfoSourceArray();
  // 控制侧边栏隐显
  @State showRightSide: boolean = false;
  // 侧栏滚动控制器
  private listScroller: Scroller = new Scroller();

  aboutToAppear(): void {
    this.fetchShopList();
    this.fetchProductCate().then(() => {
      // 调取数据后执行,将侧边栏Index根据顶部数量置空
      this.sCurrentIndex = new Array(this.productCate.length).fill(0);
      // 初始化当前tab下lazyItemList
      this.lazyItemList.initialize((this.productCate[this.currentIndex].effect_cate_list as Data[]).length);
      this.lazyItemList.updateIndex(this.sCurrentIndex[this.currentIndex])
      if (!this.lazyItemList.childCount()) {
        this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex]);
      }
      // 如果不是最后一个
      if (this.sCurrentIndex[this.currentIndex] !== this.lazyItemList.totalCount() - 1 ) {
        // 当下一页没有数据时
        if (!this.lazyItemList.nextChildCount()) {
          this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex] + 1);
        }
      }
      // 不是第一个
      if (this.sCurrentIndex[this.currentIndex] !== 0 ) {
        // 当上一页没有数据时
        if (!this.lazyItemList.preChildCount()) {
          this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex] - 1);
        }
      }
    });
  }

  @Builder
  TabBuilder(title_cn: string, title_en: string, targetIndex: number, isLast: boolean) {
    Column() {
      Text(title_cn)
        .fontColor(this.currentIndex === targetIndex ? '#333333' : '#999999')
        .fontSize(14)
      Text(title_en)
        .fontColor(this.currentIndex === targetIndex ? '#333333' : '#999999')
        .fontSize(12)
    }
    .border({
      width: {
        right: !isLast ? 1 : 0
      },
      color: '#d1cac1'
    })
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  // targetIndex: 侧边Index, Index: 顶部index
  sTabBuilder(title: string, targetIndex: number, index: number) {
    Column() {
      Text(title)
        .fontColor(this.sCurrentIndex[index] === targetIndex ? '#ff6827' : '#666056')
        .fontSize(14)
    }
    .onClick(() => {
      this.sCurrentIndex[index] = targetIndex;
      this.lazyItemList.updateIndex(targetIndex)
      if (!this.lazyItemList.childCount()) {
        this.fetchProductList(this.currentIndex, targetIndex);
      }
      // 如果不是最后一个
      if (targetIndex !== this.lazyItemList.totalCount() - 1 ) {
        // 当下一页没有数据时
        if (!this.lazyItemList.nextChildCount()) {
          this.fetchProductList(this.currentIndex, targetIndex + 1);
        }
      }
      // 不是第一个
      if (targetIndex !== 0 ) {
        // 当上一页没有数据时
        if (!this.lazyItemList.preChildCount()) {
          this.fetchProductList(this.currentIndex, targetIndex - 1);
        }
      }
      this.listScroller.scrollToIndex(targetIndex)
    })
    .height(45)
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor(this.sCurrentIndex[this.currentIndex] === targetIndex ? Color.White : '#f5f5f5')
  }

  @Builder
  ListGroupHeader(title: string) {
    Text(title)
      .fontSize(12)
      .fontColor('#666056')
      .margin({
        top: 15,
        left: 11
      })
  }

  build() {
    NavDestination() {
      Flex({ direction: FlexDirection.Column, space: { main: LengthMetrics.vp(10) } }) {
        // 顶部图片+文字
        Column() {
          Image($rawfile('OrderPage/background.png'))
            .position({
              x: 0,
              y: 0
            })
            .width('100%')
          Row() {
            Column() {
              Text('汤头泡泡')
                .fontSize(20)
                .fontColor(Color.White)
              Row({ space: 5 }) {
                Text(this.shopList.length ? this.shopList[0].title as string : '')
                  .fontSize(14)
                  .fontColor(Color.White)
                // Polyline({ width: 8, height: 12 })
                //   .points([[0, 0], [7, 5.5], [0, 11]])
                //   .fillOpacity(0)
                //   .stroke(Color.White)
                //   .strokeWidth(2)
              }
              .onClick( () => {
                // TODO 切换门店
              })
            }
            .alignItems(HorizontalAlign.Start)

            Button('门店自取', { type: ButtonType.Normal })
              .borderRadius(3)
              .fontSize(14)
              .height(25)
              .fontColor(Color.White)
              .backgroundColor('#ff6827')
          }
          .height('100%')
          .width('100%')
          .padding(15)
          .justifyContent(FlexAlign.SpaceBetween)
          .alignItems(VerticalAlign.Center)
          .backgroundColor('#4c000000')
        }
        .height(132)

        // 顶tabs栏
        Tabs() {
          ForEach(this.productCate, (item: Data, index: number) => {
            TabContent() {
              if (this.currentIndex === index) {
                Flex({ direction: FlexDirection.Column }) {
                  // 商品列表
                  Flex() {
                    List({ initialIndex: this.sCurrentIndex[index] }) {
                      ForEach(item.effect_cate_list as Data[], (sItem: Data, sIndex: number) => {
                        this.sTabBuilder(sItem.title as string, sIndex, index)
                      }, () => index.toString())
                    }
                    .height('100%')
                    .scrollBar(BarState.Off)
                    .flexBasis(88)

                    List({ scroller: this.listScroller }) {
                      // 在这里通过index跳转到相应的项
                      if (this.showRightSide) {
                        LazyForEach(this.lazyItemList, (sItem: Data[], sIndex: number) => {
                          ListItemGroup({
                                header: this.ListGroupHeader((this.productCate[index].effect_cate_list as Data[])[sIndex].title as string)
                              }) {
                            // rItem为右侧数据项
                            ForEach(sItem, (rItem: Data, rIndex: number) => {
                              ListItem() {
                                productItem({ item: rItem, isTop: this.lazyItemList.getChildData(0) === rItem })
                              }
                            })
                          }
                        })
                      }
                    }
                    .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
                      this.sCurrentIndex[this.currentIndex] = lastIndex;
                      this.lazyItemList.updateIndex(lastIndex)
                      if (!this.lazyItemList.childCount()) {
                        this.fetchProductList(this.currentIndex, lastIndex);
                      }
                      // 如果不是最后一个
                      if (lastIndex !== this.lazyItemList.totalCount() - 1 ) {
                        // 当下一页没有数据时
                        if (!this.lazyItemList.nextChildCount()) {
                          this.fetchProductList(this.currentIndex, lastIndex + 1);
                        }
                      }
                      // 不是第一个
                      if (lastIndex !== 0 ) {
                        // 当上一页没有数据时
                        if (!this.lazyItemList.preChildCount()) {
                          this.fetchProductList(this.currentIndex, lastIndex - 1);
                        }
                      }
                    })
                    // 调整组的数量
                    .cachedCount(1)
                    .flexGrow(1)
                    .sticky(StickyStyle.Header)
                    .height('100%')
                    .backgroundColor(Color.White)
                  }

                  // 底部购物车
                  Flex() {
                    // TODO 购物车功能实现
                  }
                  .height(49)
                  .width('100%')
                  .backgroundColor(Color.Blue)
                }
              }
            }
            .tabBar(this.TabBuilder(item.title as string, item.sub_title as string, index,
              index === this.productCate.length - 1 ? true : false))
          }, (item: Data) => item.title as string)
        }
        .scrollable(false)
        .onChange((index) => {
          this.currentIndex = index;
          // 重新生成itemList
          this.showRightSide = false;
          this.lazyItemList.clearDataList();
          this.lazyItemList.initialize((this.productCate[index].effect_cate_list as Data[]).length);
          this.fetchProductList(this.currentIndex, this.sCurrentIndex[this.currentIndex]);
        })
        .divider({
          strokeWidth: 10,
          color: '#f5f5f5'
        })
        .barHeight(50)
        .flexGrow(1)
        .backgroundColor(Color.White)
      }
      .backgroundColor('#f5f5f5')
    }
    .title(setTitle('门店点单'))
  }

  initializeItemList(length: number) {
    return new Array(length).fill(null).map(() => []);
  };

  async fetchShopList() {
    const res = await Api("GET", ApiUrls.shopList, {}, false)
    this.shopList = (res.results as Data).data as Data[];
  }

  async fetchProductCate() {
    const res = await Api("GET", ApiUrls.productCate, {}, false)
    this.productCate = (res.results as Data).data as Data[];
  }

  // 接受顶部和侧边栏的index,从productCate中读取数据
  async fetchProductList(topIndex: number, sideIndex: number) {
    const params: Data = {
      'cate_id': this.productCate[topIndex].id?.toString(),
      'effect_cate_id': (this.productCate[topIndex].effect_cate_list as Data[])[sideIndex].id?.toString()
    };
    const res = await Api("GET", ApiUrls.shopProductList, params, false);
    // 拼接新获取的数据到现有的列表中
    this.lazyItemList.updateIndex(sideIndex);
    this.lazyItemList.refreshDataList((res.results as Data).data as Data[])
    if (!this.showRightSide) {
      this.showRightSide = true;
    }
  }
}

@Component
struct productItem {
  @Prop item: Data;
  @Prop isTop: boolean;

  build() {
    Flex({ direction: FlexDirection.Row }) {
      Image((this.item.cover as Data[])[0].source as string)
        .flexBasis(80)
        .height(80)
      Column() {
        Text(this.item.title as string)
          .fontSize(14)
          .fontColor('#333333')
        if ((this.item.posts_list as Data[]).length > 0) {
          Text(`本膳食依据${(this.item.posts_list as Data[])[0].reference as string}所记载的${(this.item.posts_list as Data[])[0].reference_title as string},采用西点工艺制成。`)
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .fontSize(11)
            .fontColor('#666056')
            .margin({
              top: 2
            })
            .width(165)
        }
        Row() {
          Text() {
            Span('¥')
              .fontSize(12)
            Span(this.item.sku_min_price as string)
              .fontSize(17)
          }
          .fontColor('#333333')

          Button('选规格')
            .fontSize(13)
            .height(21)
            .backgroundColor('#ff6827')
        }
        .width(165)
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .height(80)
      .justifyContent(FlexAlign.SpaceBetween)
      .alignItems(HorizontalAlign.Start)
      .flexGrow(1)
      .padding({
        left: 10
      })
    }
    .padding({
      top: this.isTop ? 11 : 16,
      bottom: 15,
      left: 10
    })
    .border({
      width: {
        bottom: 1
      },
      color: '#eeeeee'
    })
  }
}

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端图片懒加载(Image Lazy Loading)和路由懒加载(Route Lazy Loading)是两种不同的优化技术,用于提高用户体验和网站性能。 1. **图片懒加载**: - 图片懒加载是一种延迟加载策略,当用户滚动到图片所在位置时才加载图片,而不是在页面加载初期就全部加载。这有助于减少初始页面加载时间,尤其是在内容较多或图片数量较大的情况下。 - 常见实现方式包括使用Intersection Observer API(Intersection Observer API是一个浏览器原生API,用于检测元素是否进入视口),或者利用HTML的`<img>`标签的`srcset`和`loading`属性,如`loading="lazy"`。 - 相关问题: 1. 如何使用Intersection Observer API实现懒加载? 2. `srcset`和`loading="lazy"`如何协同工作? 3. 图片懒加载对SEO有影响吗? 2. **路由懒加载**: - 路由懒加载是针对单页应用(SPA)的一种优化,只在用户导航到特定路由时才加载该路由对应的组件或模块,而不是一开始就下载所有可能的页面内容。 - 这通常在服务器端渲染(SSR)、动态导入(Dynamic Import)或路由预加载(Preloading)时使用,比如React的`import()`函数或Vue的`require()`。 - 相关问题: 1. 如何在Vue中实现路由懒加载? 2. SSR和路由懒加载有什么区别? 3. 使用动态导入时如何处理异步依赖? 两者都是前端性能优化的重要手段,可以帮助提高网页的加载速度和用户交互体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值