鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 13:顶部导航在窄窗口下如何简化 原创
头像 小雨同学 2026-05-27 09:09:23    发布
1 浏览 0 点赞 0 收藏


前言

我在做材料管理页的时候,最先不满意的是顶部那一排 Tab。Pura X Max 展开态里,顶部空间比较宽,全部材料、待处理、已完成、设置这几个入口都可以完整展示,甚至还能带上数量和说明文字。这个状态下,导航看起来信息很足,用户扫一眼就知道每个模块分别对应什么内容。

把窗口切到窄一些的尺寸后,顶部导航开始显得吃空间。原来四个 Tab 可以横向铺开,现在每个入口都被压成一小块,标题变短,数量角标挤在旁边,说明文字也会把导航撑高。页面内容区本来就变窄了,如果顶部继续占一大块,下面的列表能看到的内容就会少很多。

这个问题不只出现在材料管理页里,下面这些页面也经常会遇到类似情况:

  • 工作台顶部的模块切换
  • 任务列表里的状态筛选
  • 消息中心里的分类入口
  • 设置页里的分组导航
  • 审批、客户、订单这类业务后台的顶部筛选栏

这些页面有一个共同点,导航只是帮助用户切换模块,用户真正要处理的内容在下面。窗口宽的时候,导航可以多展示一点信息;窗口变窄以后,导航就要把一部分空间让给内容区。否则用户还没看到列表内容,顶部已经被标题、Tab、数量和说明占掉了一大截。

Pura X Max 的外屏是 5.4 英寸,内屏是 7.7 英寸,外屏分辨率为 1848 × 1264,内屏分辨率为 2584 × 1828,系统版本为 HarmonyOS 6.1。这个设备既有展开态的大空间,也会遇到外屏、分屏、悬浮窗这些窄窗口状态。顶部导航如果一直按照展开态来设计,窄窗口里最容易出问题的地方就是导航文字和内容区抢空间。

我这次用一个材料管理页来模拟这个场景。顶部有 4 个入口,宽屏下完整显示标题、数量和说明;窄屏下缩短文字,只保留短标题和数量;极窄窗口下不再横向铺开所有 Tab,只显示当前模块标题和一个切换按钮。这样处理以后,导航仍然能完成模块切换,内容区也能保留足够的阅读空间。

一、顶部空间会先被导航占掉

1.1 展开态里完整导航确实有用

在 Pura X Max 展开态里,顶部导航通常不会马上出问题。窗口宽度足够,4 个 Tab 同时显示完整标题、数量角标和简短说明,页面顶部还能保持一定留白。像材料管理页这种场景,用户可以直接看到“全部材料”“待处理”“已完成”“设置”,也能从数量上判断当前模块里有多少内容。

这种完整导航在宽屏下是有价值的。用户不需要点击更多按钮,也不需要猜某个短词代表什么业务。尤其是工作台、任务看板、消息中心这类页面,顶部导航不只是入口,也承担一点状态提示的作用。比如“待处理 7 项”比单独一个“待办”更能让用户判断当前页面有没有需要马上处理的内容。

示例里的展开态 Tab 会保留完整标题、数量和描述。

@Builderprivate ExpandedTabBar() {  Row({ space: 10 }) {    ForEach(this.tabs, (item: TabItem) => {      Column({ space: 5 }) {        // 宽窗口下保留完整标题、数量和说明      }    }, (item: TabItem) => item.id.toString())  }}

这段结构放在展开态里没有问题,因为每个 Tab 都能分到足够宽度。标题不会被截断,数量角标也不会挤到内容文字上,说明文字还能作为辅助信息存在。到了窄窗口,真正要调整的不是 Tab 这个能力本身,而是这一排 Tab 还应该展示多少信息。

1.2 窄窗口里导航会挤掉内容

我把宽度切到 680vp 后,顶部导航虽然还能放下 4 个 Tab,但每个 Tab 的空间已经开始紧张。完整标题继续显示时,文字会被压缩,说明文字占用第二行,整个导航区的高度也会上来。页面顶部一高,下面的列表自然就往下退,一屏里能看到的材料卡片变少。

再窄一点,比如 390vp 左右,如果还横向摆 4 个完整 Tab,每个入口都只剩一点宽度。用户看不清标题,点击区域也会变得尴尬,当前模块反而不够突出。这个时候继续保留完整导航,页面并不会因此更容易使用。

我会把顶部导航拆成两个任务来看。一个任务是告诉用户当前在哪个模块,另一个任务是提供模块切换能力。窗口足够宽时,这两个任务可以由完整 TabBar 承担;窗口变窄后,完整 TabBar 需要收缩,至少要保证当前模块能被识别,切换入口还能找到。

二、导航信息按窗口宽度收起来

2.1 expanded 保留完整导航

宽窗口下,我会保留完整导航。这个状态适合 Pura X Max 展开态,或者任何足够宽的窗口。4 个 Tab 都显示完整标题、数量和说明,用户可以直接理解每个模块的含义,也能从数量上获得一些当前状态。

示例里用 expandedWidth 控制这个状态。

private readonly expandedWidth: number = 860;

860vp 不是固定标准。真实项目里要看 Tab 数量、文字长度、是否带数量角标,以及页面顶部是否还有搜索、筛选、更多按钮。如果 Tab 名称比较长,或者顶部还要放搜索入口,这个值就要适当提高。

我在真实项目里会先把顶部导航当成一个会占用空间的区域来处理。展开态里它可以展示更多信息,到了窄窗口里,它要把一部分空间还给内容区。这个判断如果不提前做,后面就容易反复通过缩字号、压 padding、减少间距来硬撑,最后导航和内容都会变得不好读。

2.2 compact 保留短标题和数量

窗口进入中等宽度时,我不会马上把 Tab 全部收起来。这个时候 4 个入口仍然可以横向展示,但文字要缩短,说明文字先去掉,只保留短标题和数量。

示例里 compact 的门槛是:

private readonly compactWidth: number = 620;

在这个区间里,“全部材料”会变成“全部”,“待处理”会变成“待办”,“已完成”会变成“完成”。短标题保留了切换能力,数量也继续提供状态提示,但导航高度和横向占用都减少了。

@Builderprivate CompactTabBar() {  Row({ space: 8 }) {    ForEach(this.tabs, (item: TabItem) => {      Column({ space: 4 }) {        Text(item.shortTitle)        Text(item.count.toString())      }    }, (item: TabItem) => item.id.toString())  }}

这里我没有直接把 compact 做成一个更多按钮。中等宽度下,用户仍然可以承受 4 个短入口。把所有入口都收起来,切换反而会多一步。顶部导航的收缩要分层,窗口稍微窄一点时可以先减文字,再去掉说明,最后才收成当前模块和切换按钮。

这个思路放到真实项目里也比较好用。每个 Tab 预先准备完整标题和短标题,宽窗口用完整标题,窄窗口用短标题,不需要运行时临时截字符串。临时截字符串容易出现语义不完整,比如“待处理事项”被截成“待处理事”,看起来就不太像一个稳定的入口。

2.3 minimal 显示当前模块和切换入口

窗口继续缩小以后,4 个 Tab 再横向排列就不合适了。这个时候我会让导航进入 minimal,只显示当前模块标题、当前数量和一个切换按钮。

@Builderprivate MinimalTabBar() {  Row({ space: 10 }) {    Column({ space: 3 }) {      Text(this.getCurrentTab().title)      Text('共 ' + this.getCurrentTab().count.toString() + ' 项')    }​    Button('切换')  }}

这个状态适合极窄分屏、悬浮窗或者临时小窗口。用户仍然知道自己在哪个模块,也能通过“切换”进入下一个模块。它没有完整 TabBar 直观,但在小窗口里,它能给内容区留下更多空间。

这里还有一个取舍。极窄窗口里不适合继续展示 4 个入口,但当前模块不能丢。用户至少要知道自己现在看的是“全部材料”还是“待处理”。如果只剩一个“更多”按钮,当前状态会变弱;如果保留当前模块标题和数量,用户就不至于迷路。

三、导航切换收在一个入口里

3.1 宽度判断不要散在各处

真正写到页面里,我不想让每个导航组件自己判断窗口宽度。这样做短期能跑,后面一旦断点变化,修改范围会很散。示例里把布局状态集中到 getLayoutMode()

private getLayoutMode(): string {  const width = this.getEffectiveWidth();​  if (width >= this.expandedWidth) {    return 'expanded';  }​  if (width >= this.compactWidth) {    return 'compact';  }​  return 'minimal';}

这个函数只做一件事,根据当前窗口宽度返回导航状态。后面的页面结构都消费这个结果。这样写的好处是断点规则集中在一个地方,ExpandedTabBarCompactTabBarMinimalTabBar 不需要各自关心阈值。

真实项目里可以把字符串换成枚举,或者把断点抽到统一配置中。比如整个应用都使用同一套窗口状态,列表页、详情页、表单页、导航页都能共享。页面内部再根据自己的业务决定每个状态下显示什么内容。这样后面适配 Pura X Max、平板、2in1 时,不会每个页面各写一套断点。

3.2 TopNavigation 负责分发组件

示例里用 TopNavigation() 做统一入口。

@Builderprivate TopNavigation() {  if (this.isExpanded()) {    this.ExpandedTabBar()  } else if (this.isCompact()) {    this.CompactTabBar()  } else {    this.MinimalTabBar()  }}

这个写法把三种导航形态收在一个地方。页面主体不需要知道当前具体用的是完整 TabBar、短 TabBar,还是当前模块加切换按钮。它只需要调用 TopNavigation()

我在真实项目里会保留这种结构。顶部导航是一个整体,不应该把 expanded、compact、minimal 分散到页面各处。以后如果产品上要调整极窄窗口里的交互方式,比如从“切换”按钮改成下拉菜单,也只需要改 MinimalTabBar()TopNavigation() 这部分,不会影响内容区。

四、实际运行效果

这个我提供了“宽屏”“窄屏”“极窄”几个演示按钮,方便在同一台模拟器里观察顶部导航怎么变化。真实项目里可以删掉这些按钮,页面直接跟随真实窗口宽度切换。

宽屏状态下,顶部完整显示 4 个 Tab。每个 Tab 都有标题、数量和说明,适合 Pura X Max 展开态或较宽窗口。用户可以直接看到每个模块的含义,不需要额外点击。

窄屏状态下,导航仍然保留 4 个入口,但文字会缩短,说明文字被收起。全部材料变成全部,待处理变成待办,每个 Tab 只保留短标题和数量。这个状态下,导航仍然能切换模块,但不会把顶部撑得过高。

极窄状态下,完整 TabBar 会收起。顶部只显示当前模块标题、数量和一个切换按钮。点击切换按钮会按顺序切换模块。这个状态适合悬浮窗或很窄的分屏窗口,页面先保留当前内容可读,再保留基础切换能力。

我把这三张图放在一起做对比,就能看到顶部导航到底从哪里开始影响内容区。宽屏那张图里,完整 Tab 放在顶部没有什么压力,标题、数量和说明都能展开,下面的列表也还能保留足够空间。到了窄屏那张图,说明文字先收起来以后,顶部高度明显少了一截,但 4 个入口还在,用户仍然能直接切模块。再看极窄那张图,如果继续把 4 个 Tab 横向挤在一起,下面的内容区会被压得更厉害,所以我把它收成当前模块标题和一个切换按钮。

这个处理顺序基本就是我在项目里会采用的做法。先不要急着把导航全部藏起来,能保留短标题的时候就保留短标题;短标题也开始挤的时候,再收成当前模块。这样窗口从宽到窄变化时,用户不会突然失去导航入口,也不会因为顶部塞满 Tab,看不到下面真正要处理的材料列表。

五、放到真实项目如何操作

5.1 演示宽度要删掉

示例里的 previewWidth 只是为了在同一个模拟器里切换宽屏、窄屏、极窄三种状态。真实项目里不需要这些演示按钮,页面应该直接使用真实窗口宽度。

private getEffectiveWidth(): number {  if (this.previewWidth > 0) {    return this.previewWidth;  }​  return this.pageWidth;}

迁回项目时,可以直接返回 pageWidth

private getEffectiveWidth(): number {  return this.pageWidth;}

页面宽度可以继续通过 onAreaChange 写入。这里记录的是页面根容器的实际宽度,不是设备型号,也不是设备方向。对于顶部导航来说,窗口宽度比设备名称更有参考价值,因为同一台 Pura X Max 可能处在展开态、外屏、分屏、悬浮窗这些不同状态里。

5.2 Tab 数量多时要换策略

这个示例里只有 4 个 Tab,所以 compact 状态还能保留 4 个短标题。真实项目里如果模块超过 5 个,我不会继续把所有入口都横向铺开。窄窗口里横向摆太多入口,用户反而更难点到目标模块。

这种情况下,可以考虑把高频模块留在顶部,把低频模块放到更多菜单里。比如首页、任务、消息可以保留;设置、归档、规则、历史记录放到更多入口里。这样顶部导航仍然承担主要切换职责,但不会把所有模块都挤在一行。

我这里会再看一个实际指标:用户切换某个模块的频率。高频入口才值得留在顶部,低频入口可以进入更多菜单。导航不是越完整越好,尤其是窄窗口里,把所有入口都展示出来,往往会让每个入口都变得不够好点。

5.3 minimal 里不要藏掉当前状态

极窄窗口下,虽然完整 TabBar 收起了,但当前模块标题和数量不能消失。用户至少要知道自己当前在哪个模块,以及这个模块里有多少内容。示例里的 MinimalTabBar() 保留了当前模块标题、数量和切换按钮,就是为了让用户不迷路。

Text(this.getCurrentTab().title)​Text('共 ' + this.getCurrentTab().count.toString() + ' 项')​Button('切换')

真实项目里也可以把“切换”按钮换成下拉菜单、更多按钮或者底部弹层。具体用哪种交互,要看模块数量和用户切换频率。如果只有 3 到 4 个模块,顺序切换还能接受;如果模块很多,就要给用户一个明确的菜单列表。

这个地方也要避免另一个极端。minimal 状态不能只剩一个图标按钮。用户看到一个按钮,但不知道自己在哪个模块,也不知道点了会切到哪里,操作会变得很没底。当前模块标题和数量保留下来,小窗口里的导航才算还能交代当前状态。

总结

Pura X Max 的顶部导航适配,主要处理的是窗口缩窄后的信息取舍。宽屏下完整显示 Tab 标题、数量和说明,用户可以直接理解每个模块;窄屏下缩短标题并收起说明,保留切换入口和数量;极窄窗口下只显示当前模块和切换按钮,把顶部空间让给内容区。

我处理这类页面时,会先给每个 Tab 准备完整标题、短标题和数量,再决定不同窗口宽度下展示哪一层信息。导航仍然承担切换职责,但它不能在小窗口里抢走太多内容空间。尤其是悬浮窗和分屏窗口,用户更关心当前内容是否能看、当前模块是否能切,而不是顶部导航是否展示得足够完整。

附:完整代码

interface TabItem {  id: number;  title: string;  shortTitle: string;  desc: string;  count: number;}​interface MaterialItem {  id: number;  title: string;  status: string;  source: string;  time: string;  summary: string;}​@Entry@Componentstruct Index {  // 页面真实宽度,由 onAreaChange 写入  @State private pageWidth: number = 0;​  // 演示宽度,只用于在同一个模拟器里观察 expanded / compact / minimal  @State private previewWidth: number = 0;​  // 当前选中的导航模块。窗口变窄以后,minimal 状态仍然要保留当前模块  @State private selectedTab: number = 0;​  // compact 以下进入当前模块模式,expanded 以上显示完整导航  private readonly compactWidth: number = 620;  private readonly expandedWidth: number = 860;​  private readonly tabs: TabItem[] = [    {      id: 0,      title: '全部材料',      shortTitle: '全部',      desc: '查看所有整理记录',      count: 28    },    {      id: 1,      title: '待处理',      shortTitle: '待办',      desc: '需要继续确认的内容',      count: 7    },    {      id: 2,      title: '已完成',      shortTitle: '完成',      desc: '已经保存或归档',      count: 16    },    {      id: 3,      title: '设置',      shortTitle: '设置',      desc: '处理规则和提醒偏好',      count: 4    }  ];​  private readonly materials: MaterialItem[] = [    {      id: 1,      title: '社区物业缴费提醒',      status: '待处理',      source: '拍照整理',      time: '09:20',      summary: '识别到缴费截止日期、金额明细和办理地点。'    },    {      id: 2,      title: 'Pura X Max 适配会议纪要',      status: '待处理',      source: '语音转写',      time: '10:45',      summary: '整理出顶部导航、分屏窗口和横屏适配问题。'    },    {      id: 3,      title: '活动报名确认单',      status: '已完成',      source: '相册导入',      time: '11:30',      summary: '提取到报名人、联系方式、活动时间和签到地址。'    },    {      id: 4,      title: '客户需求变更记录',      status: '待处理',      source: '文本整理',      time: '13:10',      summary: '本次变更涉及首页布局、权限配置和通知策略。'    },    {      id: 5,      title: '门诊复查预约提示',      status: '已完成',      source: '拍照整理',      time: '16:40',      summary: '提取到复查时间、科室、楼层和注意事项。'    }  ];​  // Demo 中优先使用演示宽度,真实项目里可以直接返回 pageWidth  private getEffectiveWidth(): number {    if (this.previewWidth > 0) {      return this.previewWidth;    }​    return this.pageWidth;  }​  // 把导航状态集中在一个函数里,避免每个导航组件各自判断宽度  private getLayoutMode(): string {    const width = this.getEffectiveWidth();​    if (width >= this.expandedWidth) {      return 'expanded';    }​    if (width >= this.compactWidth) {      return 'compact';    }​    return 'minimal';  }​  private isExpanded(): boolean {    return this.getLayoutMode() === 'expanded';  }​  private isCompact(): boolean {    return this.getLayoutMode() === 'compact';  }​  private isMinimal(): boolean {    return this.getLayoutMode() === 'minimal';  }​  private getContentWidth(): Length {    if (this.previewWidth > 0) {      return this.previewWidth;    }​    return '100%';  }​  private getPagePadding(): number {    if (this.isExpanded()) {      return 24;    }​    if (this.isCompact()) {      return 16;    }​    return 12;  }​  private getTitleSize(): number {    if (this.isExpanded()) {      return 28;    }​    if (this.isCompact()) {      return 24;    }​    return 20;  }​  private getModeText(): string {    if (this.isExpanded()) {      return 'expanded · 完整导航';    }​    if (this.isCompact()) {      return 'compact · 简化导航';    }​    return 'minimal · 当前模块';  }​  private getModeDesc(): string {    if (this.isExpanded()) {      return '宽窗口下展示完整标题、数量和说明。';    }​    if (this.isCompact()) {      return '窄窗口下显示短标题和数量。';    }​    return '极窄窗口下显示当前模块和切换按钮。';  }​  private getCurrentTab(): TabItem {    const found = this.tabs.find((item: TabItem) => item.id === this.selectedTab);    return found ? found : this.tabs[0];  }​  private setPreview(width: number) {    this.previewWidth = width;  }​  private switchToNextTab() {    const next = this.selectedTab + 1;    this.selectedTab = next >= this.tabs.length ? 0 : next;  }​  private getVisibleMaterials(): MaterialItem[] {    if (this.selectedTab === 1) {      return this.materials.filter((item: MaterialItem) => item.status === '待处理');    }​    if (this.selectedTab === 2) {      return this.materials.filter((item: MaterialItem) => item.status === '已完成');    }​    if (this.selectedTab === 3) {      return [];    }​    return this.materials;  }​  private getStatusColor(status: string): string {    if (status === '待处理') {      return '#B25E00';    }​    return '#276749';  }​  private getStatusBgColor(status: string): string {    if (status === '待处理') {      return '#FFF4E5';    }​    return '#E7F5EE';  }​  @Builder  private PreviewButton(text: string, width: number) {    Text(text)      .fontSize(12)      .fontColor(this.previewWidth === width ? '#FFFFFF' : '#2F8F83')      .textAlign(TextAlign.Center)      .padding({ left: 10, right: 10, top: 7, bottom: 7 })      .backgroundColor(this.previewWidth === width ? '#2F8F83' : '#E6F4F1')      .borderRadius(999)      .onClick(() => {        this.setPreview(width);      })  }​  @Builder  private HeaderPanel() {    Column({ space: this.isMinimal() ? 8 : 10 }) {      Row() {        Column({ space: 4 }) {          Text('顶部导航在窄窗口下如何简化')            .fontSize(this.getTitleSize())            .fontWeight(FontWeight.Bold)            .fontColor('#111827')            .maxLines(this.isMinimal() ? 1 : 2)            .textOverflow({ overflow: TextOverflow.Ellipsis })​          Text(this.getModeText())            .fontSize(14)            .fontColor('#2F8F83')        }        .layoutWeight(1)​        if (!this.isMinimal()) {          Text('窗口 ' + Math.round(this.pageWidth).toString() + 'vp')            .fontSize(12)            .fontColor('#374151')            .padding({ left: 10, right: 10, top: 6, bottom: 6 })            .backgroundColor('#FFFFFF')            .borderRadius(999)        }      }      .width('100%')​      if (!this.isMinimal()) {        Text('演示宽度:' + Math.round(this.getEffectiveWidth()).toString() + 'vp。' + this.getModeDesc())          .fontSize(14)          .fontColor('#6B7280')          .lineHeight(21)          .maxLines(2)          .textOverflow({ overflow: TextOverflow.Ellipsis })​        Row({ space: 8 }) {          this.PreviewButton('自动', 0)          this.PreviewButton('宽屏', 960)          this.PreviewButton('窄屏', 680)          this.PreviewButton('极窄', 390)        }        .width('100%')      } else {        Row({ space: 6 }) {          this.PreviewButton('自动', 0)          this.PreviewButton('宽屏', 960)          this.PreviewButton('极窄', 390)        }        .width('100%')      }    }    .width('100%')  }​  @Builder  private ExpandedTabBar() {    Row({ space: 10 }) {      ForEach(this.tabs, (item: TabItem) => {        Column({ space: 5 }) {          Row() {            Text(item.title)              .fontSize(15)              .fontWeight(FontWeight.Medium)              .fontColor(this.selectedTab === item.id ? '#FFFFFF' : '#111827')              .maxLines(1)              .textOverflow({ overflow: TextOverflow.Ellipsis })​            Blank()​            Text(item.count.toString())              .fontSize(12)              .fontColor(this.selectedTab === item.id ? '#FFFFFF' : '#2F8F83')              .padding({ left: 7, right: 7, top: 3, bottom: 3 })              .backgroundColor(this.selectedTab === item.id ? '#33FFFFFF' : '#E6F4F1')              .borderRadius(999)          }          .width('100%')​          Text(item.desc)            .fontSize(12)            .fontColor(this.selectedTab === item.id ? '#DFF5F1' : '#6B7280')            .maxLines(1)            .textOverflow({ overflow: TextOverflow.Ellipsis })        }        .layoutWeight(1)        .padding(12)        .backgroundColor(this.selectedTab === item.id ? '#2F8F83' : '#FFFFFF')        .borderRadius(18)        .border({          width: 1,          color: this.selectedTab === item.id ? '#2F8F83' : '#E5E7EB'        })        .onClick(() => {          this.selectedTab = item.id;        })      }, (item: TabItem) => item.id.toString())    }    .width('100%')  }​  @Builder  private CompactTabBar() {    Row({ space: 8 }) {      ForEach(this.tabs, (item: TabItem) => {        Column({ space: 4 }) {          Text(item.shortTitle)            .fontSize(14)            .fontWeight(FontWeight.Medium)            .fontColor(this.selectedTab === item.id ? '#FFFFFF' : '#111827')            .maxLines(1)            .textOverflow({ overflow: TextOverflow.Ellipsis })​          Text(item.count.toString())            .fontSize(11)            .fontColor(this.selectedTab === item.id ? '#FFFFFF' : '#6B7280')        }        .layoutWeight(1)        .padding({ top: 9, bottom: 9, left: 4, right: 4 })        .backgroundColor(this.selectedTab === item.id ? '#2F8F83' : '#FFFFFF')        .borderRadius(16)        .border({          width: 1,          color: this.selectedTab === item.id ? '#2F8F83' : '#E5E7EB'        })        .onClick(() => {          this.selectedTab = item.id;        })      }, (item: TabItem) => item.id.toString())    }    .width('100%')  }​  @Builder  private MinimalTabBar() {    Row({ space: 10 }) {      Column({ space: 3 }) {        Text(this.getCurrentTab().title)          .fontSize(17)          .fontWeight(FontWeight.Bold)          .fontColor('#111827')          .maxLines(1)          .textOverflow({ overflow: TextOverflow.Ellipsis })​        Text('共 ' + this.getCurrentTab().count.toString() + ' 项')          .fontSize(12)          .fontColor('#6B7280')      }      .layoutWeight(1)​      Button('切换')        .fontSize(13)        .fontColor('#FFFFFF')        .height(34)        .width(72)        .backgroundColor('#2F8F83')        .borderRadius(17)        .onClick(() => {          this.switchToNextTab();        })    }    .width('100%')    .padding(12)    .backgroundColor('#FFFFFF')    .borderRadius(18)    .shadow({      radius: 8,      color: '#10000000',      offsetX: 0,      offsetY: 4    })  }​  @Builder  private TopNavigation() {    if (this.isExpanded()) {      this.ExpandedTabBar()    } else if (this.isCompact()) {      this.CompactTabBar()    } else {      this.MinimalTabBar()    }  }​  @Builder  private StatusPill(status: string) {    Text(status)      .fontSize(12)      .fontColor(this.getStatusColor(status))      .padding({ left: 8, right: 8, top: 4, bottom: 4 })      .backgroundColor(this.getStatusBgColor(status))      .borderRadius(999)  }​  @Builder  private MaterialCard(item: MaterialItem) {    Column({ space: this.isMinimal() ? 8 : 10 }) {      Row({ space: 8 }) {        this.StatusPill(item.status)​        Blank()​        if (!this.isMinimal()) {          Text(item.time)            .fontSize(12)            .fontColor('#6B7280')        }      }      .width('100%')​      Text(item.title)        .fontSize(this.isMinimal() ? 16 : 17)        .fontWeight(FontWeight.Medium)        .fontColor('#111827')        .maxLines(this.isMinimal() ? 1 : 2)        .textOverflow({ overflow: TextOverflow.Ellipsis })​      if (!this.isMinimal()) {        Text(item.summary)          .fontSize(13)          .fontColor('#6B7280')          .lineHeight(19)          .maxLines(this.isExpanded() ? 2 : 1)          .textOverflow({ overflow: TextOverflow.Ellipsis })​        Text(item.source)          .fontSize(12)          .fontColor('#4B5563')      }    }    .width('100%')    .padding(this.isMinimal() ? 12 : 15)    .backgroundColor('#FFFFFF')    .borderRadius(this.isMinimal() ? 16 : 18)    .border({      width: 1,      color: '#E5E7EB'    })  }​  @Builder  private SettingsPanel() {    Column({ space: 12 }) {      Text('设置')        .fontSize(18)        .fontWeight(FontWeight.Bold)        .fontColor('#111827')​      Text('这里模拟当前模块切换到设置后的内容区域。窄窗口下顶部导航不再完整铺开,内容区可以保留更多可读空间。')        .fontSize(14)        .fontColor('#6B7280')        .lineHeight(22)​      Button('打开整理规则')        .fontSize(15)        .fontColor('#FFFFFF')        .height(42)        .width('100%')        .backgroundColor('#2F8F83')        .borderRadius(21)    }    .width('100%')    .padding(16)    .backgroundColor('#FFFFFF')    .borderRadius(20)  }​  @Builder  private ContentArea() {    Scroll() {      Column({ space: 12 }) {        if (this.selectedTab === 3) {          this.SettingsPanel()        } else {          ForEach(this.getVisibleMaterials(), (item: MaterialItem) => {            this.MaterialCard(item)          }, (item: MaterialItem) => item.id.toString())​          if (this.getVisibleMaterials().length === 0) {            Text('暂无记录')              .fontSize(15)              .fontColor('#6B7280')              .width('100%')              .textAlign(TextAlign.Center)              .padding(24)              .backgroundColor('#FFFFFF')              .borderRadius(18)          }        }      }      .width('100%')      .padding({ bottom: 20 })    }    .layoutWeight(1)    .width('100%')    .edgeEffect(EdgeEffect.Spring)  }​  build() {    Column() {      Column({ space: this.isMinimal() ? 10 : 16 }) {        this.HeaderPanel()        this.TopNavigation()        this.ContentArea()      }      .width(this.getContentWidth())      .height('100%')      .padding({        left: this.getPagePadding(),        right: this.getPagePadding(),        top: this.isMinimal() ? 10 : 18,        bottom: this.isMinimal() ? 10 : 16      })    }    .width('100%')    .height('100%')    .alignItems(HorizontalAlign.Center)    .backgroundColor('#F6F7F9')    .onAreaChange((_: Area, newValue: Area) => {      const width = Number(newValue.width);      if (!Number.isNaN(width) && width > 0) {        this.pageWidth = width;      }    })  }}


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS

暂无评论数据

加载中...

发布

头像

小雨同学

产品总监、独立开发者社群主理人、资深全栈工程师,HarmonyOS应用开发者高级认证,PMP认证,CSDN博客专家,鸿蒙极客,Trae Fellow,阿里云社区专家博主、51CTO 博客专家、OpenTiny 优秀布道师、科大讯飞荣誉讲师。

13

帖子

0

提问

25

粉丝

关注
热门推荐
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 商务内容合作QQ:2291221 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255