HarmonyOS NEXT - 仿微信主界面(首页 通讯录 发现 我的)

demo 地址: https://github.com/iotjin/JhHarmonyDemo
组件对应代码实现地址
代码不定时更新,请前往github查看最新代码

本demo基于 HarmonyOS 5.0.0(12) 仿写微信主界面效果(首页 通讯录 发现 我的)
发现和我的页面通过封装的组件实现参考demo或者 HarmonyOS NEXT - 表单录入组件封装(TextInput)

效果图

请添加图片描述请添加图片描述
请添加图片描述请添加图片描述

首页

首页通过List组件实现 ,List组件官方文档
侧滑通过List组件的 swipeAction实现。 对应官方文档
右上角的弹框通过promptAction.openCustomDialog实现自定义弹窗。对应官方文档

完整代码

page

///  WxHomePage.ets
///
///  Created by iotjin on 2024/11/07.
///  description: 微信首页

import { promptAction, router } from '@kit.ArkUI'
import { BaseNavigation, JhProgressHUD, KColors } from 'JhCommon'
import { WxHomeItemType } from '../models/WxHomeModel'
import { WxHomeCell } from '../widgets/WxHomeCell'


@Entry
@Preview
@Component
export struct WxHomePage {
  @State dataArr: WxHomeItemType[] = []
  scroller: ListScroller = new ListScroller()

  aboutToAppear() {
    this.dataArr = this.getData()
    // console.log('data', JSON.stringify(this.dataArr))
  }

  build() {
    Column() {
      BaseNavigation({
        title: '微信',
        leftItem: {},
        rightImgPath: $rawfile("images/ic_nav_add.png"),
        rightItemCallBack: () => {
          console.log('点击了右侧图标')
          this.onClickRightItem()
        },
      })
      Scroll() {
        Column() {
          this.body()
        }
        .backgroundColor(KColors.wxBgColor)
      }
      .layoutWeight(1)
    }
  }

  @Builder
  body() {
    List({ scroller: this.scroller }) {
      ForEach(this.dataArr, (item: WxHomeItemType, index: number) => {
        ListItem() {
          WxHomeCell({
            model: item,
            onClickCell: () => {
              this.clickCell(item)
            },
          })
        }
        .swipeAction({
          end: {
            builder: () => {
              this.itemSwipeEnd(item.type)
            },
            actionAreaDistance: 0, // 设置组件长距离滑动删除距离阈值。 默认56
            onAction: () => {
              animateTo({ duration: 1000 }, () => {
              })
            },
          }
        })
      })
    }
    .layoutWeight(1)
    .divider({
      strokeWidth: 0.5,
      startMargin: 70,
      endMargin: 0,
      color: KColors.kLineColor
    })
  }

  @Builder
  itemSwipeEnd(type: string) {
    if (type == '0') {
      Row()
    }
    if (type == '1') {
      Row() {
        Text('删除')
          .fontSize(14)
          .fontWeight(400)
          .fontColor(Color.White)
      }
      .width('25%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.Red)
      .onClick(() => {
        JhProgressHUD.showToast('删除')
        this.scroller.closeAllSwipeActions()
      })
    }
    if (type == '2') {
      Row() {
        Row() {
          Text('标为未读')
            .fontSize(14)
            .fontColor(Color.White)
        }
        .width('50%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#74736D')
        .onClick(() => {
          JhProgressHUD.showToast('标为未读')
          this.scroller.closeAllSwipeActions()
        })

        Row() {
          Text('删除')
            .fontSize(14)
            .fontWeight(400)
            .fontColor(Color.White)
        }
        .width('50%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor(Color.Red)
        .onClick(() => {
          JhProgressHUD.showToast('删除')
          this.scroller.closeAllSwipeActions()
        })
      }
      .width('50%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.Red)
      .onClick(() => {
        JhProgressHUD.showToast('删除')
        this.scroller.closeAllSwipeActions()
      })
    }
    if (type == '3') {
      Row() {
        Row() {
          Text('不再关注')
            .fontSize(14)
            .fontColor(Color.White)
        }
        .width('50%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#74736D')
        .onClick(() => {
          JhProgressHUD.showToast('不再关注')
          this.scroller.closeAllSwipeActions()
        })

        Row() {
          Text('删除')
            .fontSize(14)
            .fontWeight(400)
            .fontColor(Color.White)
        }
        .width('50%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor(Color.Red)
        .onClick(() => {
          JhProgressHUD.showToast('删除')
          this.scroller.closeAllSwipeActions()
        })
      }
      .width('50%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor(Color.Red)
      .onClick(() => {
        JhProgressHUD.showToast('删除')
        this.scroller.closeAllSwipeActions()
      })
    }
  }

  /* openCustomDialog API12+

    创建并弹出dialogContent对应的自定义弹窗,使用Promise异步回调。

    https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-arkui-uicontext-V5#opencustomdialog12
   */

  /* promptAction.openCustomDialog API11+

    打开自定义弹窗

   https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-promptaction-V5#promptactionopencustomdialog11
   */

  private customDialogId: number = 0

  onClickRightItem() {
    promptAction.openCustomDialog({
      builder: () => {
        this.customDialogComponent()
      },
      width: '160vp',
      height: this.popData.length * 50 + 6,
      offset: { dx: -10, dy: 56 },
      alignment: DialogAlignment.TopEnd,
      // backgroundColor: '#2D2D2D',
      backgroundColor: Color.Transparent,
      cornerRadius: 5,
    }).then((dialogId: number) => {
      this.customDialogId = dialogId
    })
  }

  @Builder
  customDialogComponent() {
    Column() {
      Row() {
        Image($rawfile('images/popMenus/ic_menu_up_arrow.png')).height(6)
        Blank().width(10)
      }
      .width('100%')
      .justifyContent(FlexAlign.End)

      List() {
        ForEach(this.popData, (item: ESObject, index: number) => {
          ListItem() {
            Row() {
              Blank().width(15)
              Image(item['icon']).width(22)
              Blank().width(15)
              Text(item['text'])
                .fontColor(Color.White)
                .fontSize(16)
            }
            .width('100%')
            .height(50)
            .alignItems(VerticalAlign.Center)
          }
          .onClick(() => {
            promptAction.closeCustomDialog(this.customDialogId)
            this.clickPopCell(item['text'])
          })
        })
      }
      .enableScrollInteraction(false)
      .layoutWeight(1)
      .divider({
        strokeWidth: 1,
        startMargin: 50,
        endMargin: 0,
        color: '#E6E6E6',
      })
      .backgroundColor('#2D2D2D')
      .borderRadius(5)
    }
  }

  clickCell(item: WxHomeItemType) {
    console.log('点击cell', JSON.stringify(item))
    if (item.title == 'Demo 列表') {
      router.pushUrl({ url: 'pages/demos/DemoListPage' })
    } else if (item.title == '微信运动') {
      router.pushUrl({ url: 'pages/one/pages/WxMotionPage' })
    } else if (item.title == '订阅号消息') {
      router.pushUrl({ url: 'pages/one/pages/WxSubscriptionNumberListPage' })
    } else if (item.title == 'QQ邮箱提醒') {
      router.pushUrl({ url: 'pages/one/pages/WxQQMessagePage' })
    } else {
      router.pushUrl({ url: 'pages/demos/DemoListPage' })
    }
  }

  clickPopCell(text: string) {
    console.log('点击', JSON.stringify(text))
    if (text == '添加朋友') {
      router.pushUrl({ url: 'pages/two/pages/WxAddFriendPage' })
    }
  }

  getData() {
    let dataArr: WxHomeItemType[] = [
      {
        'title': 'Demo 列表',
        'subtitle': '点击跳转demo列表',
        'img': 'images/ic_demo1.png',
        'time': '',
        'isNew': false,
        'type': '0',
      },
      {
        'title': '微信运动',
        'subtitle': '[应用消息]',
        'img': 'wechat/home/wechat_motion.png',
        'time': '22:23',
        'isNew': true,
        'type': '1',
      },
      {
        'title': '订阅号消息',
        'subtitle': '新闻联播开始啦',
        'img': 'wechat/home/ic_subscription_number.png',
        'time': '19:00',
        'isNew': true,
        'type': '1',
      },
      {
        'title': 'QQ邮箱提醒',
        'subtitle': '您有一封新的邮件,请前往查收',
        'img': 'wechat/home/Ic_email.png',
        'time': '17:30',
        'isNew': false,
        'type': '3',
      },
      {
        'title': '张三',
        'subtitle': '欢迎欢迎',
        'img': 'images/picture/touxiang_1.jpeg',
        'time': '17:30',
        'isNew': false,
        'type': '2',
      },
      {
        'title': '李四',
        'subtitle': 'hello',
        'img': 'images/picture/touxiang_2.jpeg',
        'time': '17:30',
        'isNew': false,
        'type': '2',
      },
      {
        'title': '王五',
        'subtitle': '[图片]',
        'img': 'images/picture/touxiang_3.jpeg',
        'time': '17:30',
        'isNew': false,
        'type': '2',
      },
      {
        'title': '赵六',
        'subtitle': '[动画表情]',
        'img': 'images/picture/touxiang_4.jpeg',
        'time': '17:30',
        'isNew': false,
        'type': '2',
      },
      {
        'title': '微信团队',
        'subtitle': '安全登录提醒',
        'img': 'wechat/home/ic_about.png',
        'time': '2020/8/8',
        'isNew': false,
        'type': '1',
      },
    ];
    return dataArr;
  }

  popData: ESObject = [
    {
      'text': '发起群聊',
      'icon': $rawfile('images/popMenus/ic_chat.png'),
    },
    {
      'text': '添加朋友',
      'icon': $rawfile('images/popMenus/ic_add.png'),
    },
    {
      'text': '扫一扫',
      'icon': $rawfile('images/popMenus/ic_scan.png'),
    },
    {
      'text': '收付款',
      'icon': $rawfile('images/popMenus/ic_pay.png'),
    },
  ];
}

view

import { WxHomeModel } from "../models/WxHomeModel";


@Preview
@Component
export struct WxHomeCell {
  model: WxHomeModel = new WxHomeModel();
  public onClickCell?: (model: WxHomeModel) => void // 点击cell

  build() {
    Row() {
      Row() {
        // Image($rawfile(this.model.img))
        //   .width(55).height(55)
        //   .borderRadius(5)
        //   .clip(true)

        Badge({ value: '', position: { x: 55 - 5, y: -5 }, style: { badgeSize: this.model.isNew ? 10 : 0, badgeColor: '#FA2A2D' } }) {
          Image($rawfile(this.model.img))
            .width(55)
            .height(55)
            .borderRadius(5)
            .clip(true)
        }
      }
      .justifyContent(FlexAlign.Center)
      .alignItems(VerticalAlign.Center)
      .width(70)
      .height(70)

      Column() {
        Blank().height(8)
        Row() {
          Flex() {
            Text(this.model.title)
              .flexGrow(70)
              .fontSize(18)
            Text(this.model.time)
              .flexGrow(30)
              .fontSize(13)
              .fontColor(Color.Gray)
              .textAlign(TextAlign.End)
            Blank().width(10)
          }
        }
        .justifyContent(FlexAlign.Start)

        Blank().height(8)

        Text(this.model.subtitle)
          .fontSize(15)
          .fontColor(Color.Gray)
      }
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.Start)
      .layoutWeight(1)
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(VerticalAlign.Top)
    .width('100%')
    .height(70)
    .backgroundColor(Color.White)
    .onClick(() => this.onClickCell?.(this.model))
  }
}

model

export class WxHomeModel {
  title?: string
  subtitle?: string
  img?: string
  time?: string
  isNew?: boolean
  type?: string // 0,1,2,3
}

// type 0 没有侧滑
// type 1 删除
// type 2 标为未读和删除
// type 3 不在关注和删除

export interface WxHomeItemType {
  title?: string
  subtitle?: string
  img?: string
  time?: string
  isNew?: boolean
  type?: string
}

通讯录

通讯录通过List组件分组列表实现 ,List组件官方文档
侧滑通过List组件的 swipeAction实现。 对应官方文档
右侧的索引通过AlphabetIndexer组件实现, 对应官方文档

初始数据是对象数组,因为通讯录的页面是分组的结构,并且List组件的分组需要转成每组下面是对应组的数组,因此要对原始数据根据key分组处理

  • 页面

在这里插入图片描述

  • 初始数据结构:
[
    {
      "id": 0,
      "name": "张芳",
      "phone": "13564303126",
      "sex": "0",
      "region": "商丘市",
      "province": "山东省",
      "city": "黔南布依族苗族自治州",
      "area": "阿拉山口市",
      "label": "label0",
      "color": "#f2e979",
      "img": "http://dummyimage.com/100x100/f2e979&text=hi",
      "avatarUrl": "https://gitee.com/iotjh/Picture/raw/master/lufei.png",
      "isStar": false
    },
    ...
 ]
  • List 分组需要的数据结构类似于
    在这里插入图片描述

分组的算法(数组按指定key分组)

// 数组按指定key分组
function groupListByKey<T>(array: T[], key: string): ESObject[] {
  const result: ESObject[] = [];
  const groupMap: Map<string, T[]> = new Map();

  // 遍历数组,将每个元素按照key分组
  array.forEach((item: ESObject) => {
    const groupValue = String(item[key]);
    if (!groupMap[groupValue]) {
      groupMap[groupValue] = [];
    }
    groupMap[groupValue].push(item);
  });

  Object.keys(groupMap).forEach((key) => {
    result.push({ groupName: key, data: groupMap[key] });
  });
  return result;
}

完整代码

page

///  WxContactsPage.ets
///
///  Created by iotjin on 2024/11/20.
///  description: 微信通讯录

import { router } from '@kit.ArkUI'
import { pinyin } from 'pinyin-pro';
import { BaseNavigation, JhProgressHUD, KColors } from 'JhCommon'
import { WxContactsModel } from '../models/WxContactsModel'
import { WxContactsCell } from '../widgets/WxContactsCell'
import data from '../../../pages/res/wx_contacts.json'


@Entry
@Preview
@Component
export struct WxContactsPage {
  @State dataArr: ESObject[] = []
  // @State tagIndexList: string[] = ['🔍', '★', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
  //   'H', 'I', 'J', 'K', 'L', 'M', 'N',
  //   'O', 'P', 'Q', 'R', 'S', 'T', 'U',
  //   'V', 'W', 'X', 'Y', 'Z', '#']
  @State tagIndexList: string[] = []
  // scroller: Scroller = new Scroller()
  scroller: ListScroller = new ListScroller()
  searchController: SearchController = new SearchController()
  @State selectedIndex: number = 0
  @State isShowTagPopup: boolean = false
  @State contactsCount: string = ''

  aboutToAppear() {
    this.dataArr = this.loadData()
    // console.log('data', JSON.stringify(this.dataArr))

    let tempList: string[] = [];
    for (let i = 0; i < this.dataArr.length; i++) {
      const item: ESObject = this.dataArr[i];
      tempList.push(item['groupName'])
    }
    this.tagIndexList = tempList;
  }

  build() {
    Column() {
      BaseNavigation({
        isGradient: true,
        title: '通讯录',
        leftItem: {},
        rightImgPath: $rawfile("images/ic_addFriend.png"),
        rightItemCallBack: () => {
          this.searchController.stopEditing()
          router.pushUrl({ url: 'pages/two/pages/WxAddFriendPage' })
        },
      })
      this.body()
    }
  }

  @Builder
  body() {
    Stack({ alignContent: Alignment.End }) {
      List({ space: 0, initialIndex: 0, scroller: this.scroller }) {
        ForEach(this.dataArr, (groupItem: ESObject, index: number) => {
          ListItemGroup({ header: this.itemHeader(groupItem.groupName) }) {
            ForEach(groupItem.data, (cellItem: string, index2: number) => {
              ListItem() {
                WxContactsCell({
                  index: index,
                  model: cellItem as WxContactsModel,
                  searchController: this.searchController,
                  onClickCell: (model: WxContactsModel) => {
                    console.log('点击cell', JSON.stringify(cellItem))
                    this.searchController.stopEditing()
                    router.pushUrl({ url: 'pages/two/pages/WxUserInfoPage', params: { data: cellItem } })
                  },
                  onClickTopCell: (model: ESObject) => {
                    this.searchController.stopEditing()
                    this.clickCell(model['title'])
                  },
                })
              }
              .swipeAction({
                end: {
                  builder: () => {
                    if (index != 0) {
                      this.itemSwipeEnd()
                    }
                  },
                  actionAreaDistance: 0, // 设置组件长距离滑动删除距离阈值。 默认56
                  onAction: () => {
                    animateTo({ duration: 1000 }, () => {
                    })
                  },
                }
              })
            }, (item: string) => item)
          }
          // 每行之间的分界线
          .divider({
            strokeWidth: 0.6,
            startMargin: 65,
            endMargin: 0,
            color: KColors.kFormLineColor,
          })
        })
        ListItem() {
          this.footer()
        }
      }
      .layoutWeight(1)
      .sticky(StickyStyle.Header | StickyStyle.Footer)
      .scrollBar(BarState.Off)
      .onScrollIndex((firstIndex: number, lastIndex: number) => {
        // console.log('firstIndex', JSON.stringify(firstIndex))
        this.selectedIndex = firstIndex
      })

      AlphabetIndexer({ arrayValue: this.tagIndexList, selected: 0 })
        .color(Color.Black)// 文字颜色
        .selectedColor(Color.White)// 选中项文字颜色
        .popupColor(Color.White)// 提示弹窗文字颜色
        .selectedBackgroundColor(KColors.kThemeColor)// 选中项背景颜色
        .popupBackground('#C9C9C9')// 提示弹窗背景色
        .usingPopup(this.isShowTagPopup)// 是否使用提示弹窗
        .selectedFont({ size: 16, weight: FontWeight.Bolder })// 选中项字体样式
        .popupFont({ size: 30, weight: FontWeight.Bolder })// 弹出框内容的字体样式
        .font({ size: 14 })// 字母索引条默认字体样式
        .itemSize(22)// 字母索引条字母区域大小
        .alignStyle(IndexerAlign.END)// 字母索引条弹框的对齐样式,支持弹窗显示在索引条右侧和左侧.默认值: IndexerAlign.END
        .popupPosition({ x: 60, y: 48 })
        .itemBorderRadius(11)//索引项背板圆角半径
        .popupBackgroundBlurStyle(BlurStyle.NONE)// 设置提示弹窗的背景模糊材质
        .selected(this.selectedIndex)
        .onSelect((index: number) => {
          // console.log('index', index)
          console.log(this.tagIndexList[index] + ' Selected!')
          // const findIndex = this.dataArr.findIndex((item: ESObject) => item['groupName'] == this.tagIndexList[index]);
          // console.log('findIndex', JSON.stringify(findIndex))
          this.scroller.scrollToIndex(index)
          this.isShowTagPopup = true;
          setTimeout(() => {
            this.isShowTagPopup = false;
          }, 3000)
        })
        .onPopupSelect((index: number) => {
          console.info('onPopupSelected:' + index)
        })
    }
    .layoutWeight(1)
  }

  @Builder
  itemHeader(text: string) {
    if (text == '🔍') {
      Row()
    } else {
      Row() {
        Text(text == '★' ? '★ 星标朋友' : text)
          .padding({ left: 15 })
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.isFloat(text) ? KColors.wxPayColor : '#777777')
      }
      .width("100%")
      .height(30)
      .backgroundColor(KColors.wxBgColor)
    }
  }

  isFloat(text: string) {
    return this.tagIndexList[this.selectedIndex] == text
  }

  @Builder
  itemSwipeEnd() {
    Row() {
      Text('备注')
        .fontSize(14)
        .fontColor(Color.White)
    }
    .width('25%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#74736D')
    .onClick(() => {
      JhProgressHUD.showToast('点击备注')
      this.scroller.closeAllSwipeActions()
    })
  }

  @Builder
  footer() {
    Row() {
      Text(`${this.getData().length} 位朋友及联系人`)
        .fontSize(16)
        .fontColor(Color.Gray)
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(KColors.wxBgColor)
  }

  clickCell(text: string) {
    console.log('点击', JSON.stringify(text))
    if (text == '新的朋友') {
      router.pushUrl({ url: 'pages/two/pages/WxNewFriendPage' })
    }
    if (text == '群聊') {
      router.pushUrl({ url: 'pages/two/pages/WxGroupChatPage' })
    }
  }

  loadData() {
    // 原始数据
    const dataArr: ESObject[] = this.getData()

    // 处理数据
    let tempList: ESObject[] = [];
    for (let i = 0; i < dataArr.length; i++) {
      const item: ESObject = dataArr[i]
      const pinyinStr = pinyin(item.name, { toneType: 'none' })
      const tag = pinyinStr.substring(0, 1).toUpperCase();
      item.namePinyin = pinyinStr;
      if (item.isStar == true) {
        item.tagIndex = '★';
      } else if (new RegExp('[A-Z]').test(tag)) {
        item.tagIndex = tag;
      } else {
        item.tagIndex = '#';
      }
      tempList.push(item);
    }

    // 根据A-Z排序
    tempList.sort((a: ESObject, b: ESObject) => {
      if (a['tagIndex'] === b['tagIndex']) {
        return a['name'].localeCompare(b['name']) as number
      }
      if (a['tagIndex'] === "#") {
        return 1
      }
      if (b['tagIndex'] === "#") {
        return -1
      }
      return a['tagIndex'].localeCompare(b['tagIndex']) as number
    });

    // 把星标移到最前
    tempList.forEach((item: ESObject, index) => {
      if (item.isStar == true) {
        tempList.unshift(...tempList.splice(index, 1));
      }
    });

    // 添加 header
    tempList.unshift({
      name: "header",
      tagIndex: "🔍"
    });

    // 改成分组数据
    const newArr: ESObject[] = groupListByKey(tempList, 'tagIndex');
    return newArr
  }

  getData() {
    let dataArr: ESObject[] = data['data']
    return dataArr;
  }
}

// 数组按指定key分组
function groupListByKey<T>(array: T[], key: string): ESObject[] {
  const result: ESObject[] = [];
  const groupMap: Map<string, T[]> = new Map();

  // 遍历数组,将每个元素按照key分组
  array.forEach((item: ESObject) => {
    const groupValue = String(item[key]);
    if (!groupMap[groupValue]) {
      groupMap[groupValue] = [];
    }
    groupMap[groupValue].push(item);
  });

  Object.keys(groupMap).forEach((key) => {
    result.push({ groupName: key, data: groupMap[key] });
  });
  return result;
}

view

import { JhSetCell, KColors } from "JhCommon";
import { WxContactsModel } from "../models/WxContactsModel";

const _cellHeight = 50
const _imgWH = 40
const _lRSpace = 15

@Preview
@Component
export struct WxContactsCell {
  index: number = 0;
  model: WxContactsModel = new WxContactsModel();
  searchController: SearchController = new SearchController()
  public onClickCell?: (model: WxContactsModel) => void // 点击cell
  public onClickTopCell?: (model: ESObject) => void // 点击cell

  build() {
    if (this.index == 0) {
      this.header()
    } else {
      Row() {
        Row() {
          // Image(this.model.avatarUrl)
          //   .width(_imgWH).height(_imgWH)
          //   .borderRadius(5)
          //   .clip(true)
          Row() {
            Text(this.model.name?.substring(0, 1))
              .fontColor(Color.White)
              .fontSize(20)
          }
          .justifyContent(FlexAlign.Center)
          .alignItems(VerticalAlign.Center)
          .width(_imgWH)
          .height(_imgWH)
          .borderRadius(5)
          .backgroundColor(this.model.color)
        }
        .justifyContent(FlexAlign.Center)
        .alignItems(VerticalAlign.Center)
        .width(_imgWH + _lRSpace * 2)
        .height(_cellHeight)

        Column() {
          Text(this.model.name)
            .fontSize(15)
            .fontColor(Color.Black)
        }
        .alignItems(HorizontalAlign.Start)
        .justifyContent(FlexAlign.Center)
        .height('100%')
        .layoutWeight(1)
      }
      .justifyContent(FlexAlign.Start)
      .alignItems(VerticalAlign.Top)
      .width('100%')
      .height(_cellHeight)
      .backgroundColor(Color.White)
      .onClick(() => this.onClickCell?.(this.model))
    }

  }

  @Builder
  header() {
    Column() {
      Row() {
        Search({ value: '', placeholder: '搜索', controller: this.searchController })
          .id('WxContactsPageSearch_ID')
          .borderRadius(5)
          .margin(10)
          .onSubmit((value: string) => {
            console.log('搜索框 onSubmit 文字:', JSON.stringify(value))
          })
          .onChange((value: string) => {
            console.log('搜索框 onChange 文字:', JSON.stringify(value))
          })
      }
      .backgroundColor(KColors.kBgColor)

      ForEach(this.topData, (item: ESObject, index: number) => {
        // JhSetCell({
        //   leftIcon: item['imgUrl'],
        //   leftImgWH: 40,
        //   title: item['title'],
        //   titleWidth: 300,
        //   lineLeftEdge: 65,
        //   hiddenArrow: true,
        //   cellHeight: _cellHeight,
        // })
        JhSetCell({
          leftWidget: (): void => this.leftBuilder(item),
          leftImgWH: 40,
          title: item['title'],
          titleWidth: 300,
          lineLeftEdge: 65,
          hiddenArrow: true,
          cellHeight: _cellHeight,
          clickCallBack: () => {
            this.onClickTopCell?.(item)
          },
        })

      })
    }
  }

  @Builder
  leftBuilder(item: ESObject) {
    Image(item['imgUrl'])
      .width(_imgWH).height(_imgWH)
      .borderRadius(5)
      .clip(true)
  }

  topData: ESObject = [
    {
      'title': '新的朋友',
      'imgUrl': $rawfile('wechat/contacts/ic_new_friend.png'),
    },
    {
      'title': '群聊',
      'imgUrl': $rawfile('wechat/contacts/ic_group_chat.png'),
    },
    {
      'title': '标签',
      'imgUrl': $rawfile('wechat/contacts/ic_tag.png'),
    },
    {
      'title': '公众号',
      'imgUrl': $rawfile('wechat/contacts/ic_public_account.png'),
    },
  ];
}

model

export class WxContactsModel {
  id?: number
  name?: string
  phone?: string
  sex?: string
  region?: string
  province?: string
  city?: string
  area?: string
  label?: string
  color?: string
  img?: string
  avatarUrl?: string
  isStar?: boolean
  // 新增
  namePinyin?: string
  tagIndex?: string
}

发现和我的页面通过封装的组件实现参考demo或者 HarmonyOS NEXT - 表单录入组件封装(TextInput)

发现 代码

///  WxDiscoverPage.ets
///
///  Created by iotjin on 2024/11/05. 
///  description: 发现


import { BaseNavigation, JhProgressHUD, JhSetCell, KColors } from 'JhCommon'
import { router } from '@kit.ArkUI';

const _cellH = 55.0;
const _leftSpace = 50.0;
const _rowSpace = 6.0;

@Entry
@Preview
@Component
export struct WxDiscoverPage {
  build() {
    Column() {
      BaseNavigation({ title: '发现', bgColor: Color.Transparent, leftItem: {} })
      Scroll() {
        Column() {
          this.body()
        }
      }
    }
    .backgroundColor(KColors.wxBgColor)
  }

  @Builder
  body() {
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_social_circle.png"),
      title: '朋友圈',
      hiddenLine: true,
      rightWidget: (): void => this.rightBuilder(),
      clickCallBack: () => {
        this.clickCell('朋友圈')
      },
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_video_number.png"),
      title: '视频号',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('视频号'),
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      lineLeftEdge: _leftSpace,
      leftIcon: $rawfile("wechat/discover/ic_quick_scan.png"),
      title: '扫一扫',
      clickCallBack: (): void => this.clickCell('扫一扫'),
    })
    JhSetCell({
      cellHeight: _cellH,
      lineLeftEdge: _leftSpace,
      leftIcon: $rawfile("wechat/discover/ic_shake_phone.png"),
      title: '摇一摇',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('摇一摇'),
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      lineLeftEdge: _leftSpace,
      leftIcon: $rawfile("wechat/discover/ic_feeds.png"),
      title: '看一看',
      clickCallBack: (): void => this.clickCell('看一看'),
    })
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_quick_search.png"),
      title: '搜一搜',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('搜一搜'),
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_people_nearby.png"),
      title: '附近的人',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('附近的人'),
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      lineLeftEdge: _leftSpace,
      leftIcon: $rawfile("wechat/discover/ic_shopping.png"),
      title: '购物',
      clickCallBack: (): void => this.clickCell('购物'),
    })
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_game_entry.png"),
      title: '游戏',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('游戏'),
    })
    Blank().height(_rowSpace)
    JhSetCell({
      cellHeight: _cellH,
      leftIcon: $rawfile("wechat/discover/ic_mini_program.png"),
      title: '小程序',
      hiddenLine: true,
      clickCallBack: (): void => this.clickCell('小程序'),
    })
    Blank().height(15)
  }

  @Builder
  rightBuilder() {
    // Image('https://gitee.com/iotjh/Picture/raw/master/lufei.png').height(30).width(30)
    Row() {
      Badge({ value: '', position: BadgePosition.RightTop, style: { badgeSize: 6, badgeColor: '#FA2A2D' } }) {
        Image($rawfile('images/lufei.png'))
          .width(30).height(30)
          .margin({ top: 2.5, left: 2.5 })
      }
      .width(35).height(35)

      // .backgroundColor(Color.Green)
    }
  }

  // 点击cell
  clickCell(title: string) {
    console.log('点击cell', title)
    if (title === '朋友圈') {
      router.pushUrl({ url: 'pages/three/pages/WxFriendsCirclePage' })
    } else {
      JhProgressHUD.showText(`点击 ${title}`)
    }
  }
}

我的 代码

///  WxMinePage.ets
///
///  Created by iotjin on 2024/11/06.
///  description:  微信我的

import { BaseNavigation, JhAESPreferencesUtils, JhSetCell, KColors, kUserDefault_UserInfo } from 'JhCommon'
import { UserModel } from '../../model/UserModel';
import { router } from '@kit.ArkUI';

const _cellH = 55.0;
const _leftSpace = 50.0;
const _rowSpace = 6.0;

@Entry
@Preview
@Component
export struct WxMinePage {
  userInfo?: UserModel;

  aboutToAppear() {
    let useInfo = JhAESPreferencesUtils.getModel(kUserDefault_UserInfo)
    console.log('useInfo: ', JSON.stringify(useInfo))
    this.userInfo = useInfo as UserModel
    console.log('this.userInfo', JSON.stringify(this.userInfo))
  }

  build() {
    Stack({ alignContent: Alignment.TopEnd }) {
      this.body()
      BaseNavigation({
        title: '',
        bgColor: Color.Transparent,
        leftItem: {},
        rightImgPath: $rawfile("images/ic_set_black.png"),
        rightItemCallBack: () => {
          this.clickCell('设置')
        },
      })
        .hitTestBehavior(HitTestMode.Transparent)
    }
    .backgroundColor(KColors.wxBgColor)
  }

  @Builder
  body() {
    List() {
      this.header()
      ListItem() {
        Column() {
          Blank().height(_rowSpace)
          JhSetCell({
            cellHeight: _cellH,
            leftIcon: $rawfile("wechat/mine/ic_wallet.png"),
            title: '服务',
            hiddenLine: true,
            clickCallBack: (): void => this.clickCell('服务'),
          })
          Blank().height(_rowSpace)
          JhSetCell({
            cellHeight: _cellH,
            lineLeftEdge: _leftSpace,
            leftIcon: $rawfile("wechat/mine/ic_collections.png"),
            title: '收藏',
          })
          JhSetCell({
            cellHeight: _cellH,
            lineLeftEdge: _leftSpace,
            leftIcon: $rawfile("wechat/mine/ic_album.png"),
            title: '相册',
          })
          JhSetCell({
            cellHeight: _cellH,
            lineLeftEdge: _leftSpace,
            leftIcon: $rawfile("wechat/mine/ic_cards_wallet.png"),
            title: '卡包',
          })
          JhSetCell({
            cellHeight: _cellH,
            lineLeftEdge: _leftSpace,
            leftIcon: $rawfile("wechat/mine/ic_emotions.png"),
            title: '表情',
            hiddenLine: true,
          })
          Blank().height(_rowSpace)
          JhSetCell({
            cellHeight: _cellH,
            leftIcon: $rawfile("wechat/mine/ic_settings.png"),
            title: '设置',
            hiddenLine: true,
            clickCallBack: (): void => this.clickCell('设置'),
          })
          Blank().height(_rowSpace)
          JhSetCell({
            cellHeight: _cellH,
            leftIcon: $rawfile("images/ic_about.png"),
            title: '检查更新',
            text: '有新版本',
            textStyle: { fontSize: 14, fontColor: Color.Red },
            hiddenLine: true,
            clickCallBack: (): void => this.clickCell('showUpdateDialog'),
          })
          Blank().height(50)
        }
      }
    }.backgroundColor(Color.Transparent)
  }

  @Builder
  header() {
    Row() {
      // Image('https://gitee.com/iotjh/Picture/raw/master/lufei.png').width(75).height(75).borderRadius(10)
      Image(this.userInfo?.avatarUrl).width(75).height(75).borderRadius(10)
        .onClick(() => {
          console.log('点击头像==  ${this.userInfo?.avatarUrl}')
          this.clickCell('个人信息')
        })
      Column() {
        Text(this.userInfo?.userName)
          .fontSize(28)
          .fontWeight(FontWeight.Medium)
          .textAlign(TextAlign.Start)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .width('100%')// .backgroundColor(Color.Pink)
          .onClick(() => {
            console.log('点击昵称==  $\{model.userName}')
            this.clickCell('个人信息')
          })
        Row() {
          Text('微信号:abc').fontSize(17).fontColor(Color.Gray)
          Blank()
          // .backgroundColor(Color.Orange)
          Image($rawfile('wechat/mine/ic_setting_myQR.png')).width(18).height(18).margin({ right: 20 })
          Image($rawfile('JhForm/ic_arrow_right.svg')).width(25).height(25)
        }
        .margin({ top: 10 })
        .width('100%')
        // .backgroundColor(Color.Yellow)
        .onClick(() => {
          this.clickCell('个人信息')
        })

      }
      .layoutWeight(1)
      .padding({ left: 16 })

      // .backgroundColor(Color.Red)
    }
    .backgroundColor(Color.White)
    .padding({
      left: 15,
      right: 15,
      top: 40,
      bottom: 30
    })
  }

  // 点击cell
  clickCell(title: string) {
    console.log('点击cell', title)
    if (title === '服务') {
      router.pushUrl({ url: 'pages/four/pages/WxPayPage' })
    }
    if (title === '个人信息') {
      router.pushUrl({ url: 'pages/four/pages/WxPersonInfoPage' })
    }
    if (title === '设置') {
      router.pushUrl({ url: 'pages/four/pages/SetPage' })
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西半球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值