鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 12:悬浮窗下保留最小可用界面 原创
头像 小雨同学 2026-05-26 09:13:46    发布
1 浏览 0 点赞 0 收藏

前言

我在看一个整理结果页的小窗状态时,第一眼注意到的是按钮位置太靠后了。全屏状态下,这个页面看起来信息很全,标题、状态、摘要、来源、时间、标签、识别内容、处理建议、主按钮和次按钮都能放下。到了悬浮窗尺寸以后,这些内容仍然按全屏页面的顺序往下排,用户要先看完一堆说明,才能找到真正要点的那个按钮。

这个细节在 Pura X Max 上比较典型。展开态适合放更多信息,分屏和自由窗口会让页面变窄,悬浮窗则更像一个临时处理的小区域。Pura X Max 外屏是 5.4 英寸,内屏是 7.7 英寸,外屏分辨率为 1848 × 1264,内屏分辨率为 2584 × 1828,系统版本为 HarmonyOS 6.1。这个设备既有展开态的大屏场景,也有分屏和悬浮窗里的小窗口场景,如果页面一直按全屏状态设计,小窗里最先受影响的通常就是主操作。

我这次处理的是一个整理结果确认页。它在全屏下可以展示完整信息,但在悬浮窗里,用户大概率只是想确认当前记录、点一下处理、把这件事先放下。这个时候页面不需要带着所有字段一起进入小窗,先让用户知道当前处理对象、当前状态,以及下一步应该点哪里,实际使用里已经够了。

这次适配基于下面这个环境:

  • 设备形态:Pura X Max 阔折叠设备
  • 系统版本:HarmonyOS 6.1
  • 页面类型:整理结果页、提醒确认页、轻量处理页
  • 技术方向:窗口宽度判断、信息收缩、操作区保留、full / compact / minimal 三档状态

我给页面增加了一个 minimal 状态。它的目的很直接,小窗口里只留下当下要用的东西。标题、状态、主按钮留下;摘要、元信息、识别详情、辅助入口先收起来。窗口变宽以后,这些内容再逐步回到页面里。这样处理以后,小窗不会变成一个缩小后的长页面,用户也不用在里面找按钮。

一、小窗里先把任务放到前面

1.1 全屏内容搬进去以后会挡住按钮

很多页面在全屏下看起来内容并不多,因为屏幕有足够空间。标题放一行,摘要放两三行,下面再放来源、时间、标签,识别内容继续往下排,最后再放主按钮和次按钮。用户全屏使用时,从上往下看一遍内容,最后点按钮,这个顺序没有太大阻碍。

到了悬浮窗尺寸,这个顺序就会变成负担。窗口只有一小块区域,标题、摘要、来源、时间、标签、识别内容、处理建议都挤在一起,按钮自然就被挤到后面。用户本来只是想快速处理一个提醒,现在却要先在小窗里重新读一遍完整页面,这和悬浮窗里的实际动作并不匹配。

我在处理这类页面时,会先把任务拆得更具体一点。用户打开这个小窗时,最可能完成哪件事?如果只是确认一条记录、保存一个提醒、处理一个结果,页面里最应该留下的就不是所有信息,而是当前对象、当前状态和主操作。

比如这个整理结果页,悬浮窗里真正要保留的是这些内容:

  • 记录标题,让用户知道自己正在处理什么
  • 状态标签,让用户知道这条记录是否还在待处理
  • 主按钮,让用户可以直接完成当前动作
  • 少量反馈,比如已经操作了几次

摘要、来源、时间、标签、识别原文、处理建议这些内容仍然有价值,但它们不适合排在主操作前面。窗口恢复到更宽状态后,再把它们放回来,页面的任务顺序会更贴近用户在小窗口里的真实动作。

1.2 小窗口承担的是临时处理

悬浮窗里的操作一般不会持续很久。用户可能一边看其他内容,一边把这个应用放成小窗,只想顺手处理一个提醒;也可能在分屏里切出一个小窗口,确认一条识别结果是否要保存。这种使用方式和全屏阅读完全不同。

我会把悬浮窗看成一个临时处理入口。用户把页面缩成小窗时,大概率不是为了完整阅读一条记录,而是想确认当前事项能不能处理、要不要保存、是否需要稍后再看。这个时候如果还把全屏里的摘要、来源、识别内容、处理建议都按原顺序塞进去,主按钮会被压到很靠后的位置。页面看起来信息完整,但用户真正要做的动作反而被放到了后面。

这里需要一张示意图把问题抽出来。真实截图有时会被具体样式干扰,示意图可以直接表达小窗口里内容太多、主操作被挤到下方这个现象。看这张图时,不用关注颜色和卡片细节,主要看内容块和按钮的位置关系。

这张图也可以作为后面代码判断的铺垫。后面的 minimal 状态,其实就是针对这张图里暴露出来的页面顺序做处理。我们不是在小窗口里压缩所有内容,而是把主操作提前,把辅助信息往后收。

二、信息分成三档

2.1 full 承载完整上下文

宽窗口下,我会保留完整信息。这个状态适合全屏、展开态或者较宽的自由窗口,页面可以展示标题、状态、摘要、元信息、识别内容、处理建议和多个操作入口。用户有足够空间看上下文,也能从辅助面板里看到更多补充信息。

在示例里,full 状态会显示主卡片和右侧辅助面板。主卡片负责当前记录,右侧面板放建议、关联入口和状态补充。这个状态适合用户停下来仔细处理一条记录,比如确认识别内容是否准确,或者判断要不要把这条记录保存成待办。

private readonly fullWidth: number = 840;

这里的 840vp 是示例里的门槛,不是固定标准。如果页面字段更多,或者右侧说明卡片更宽,这个值可以继续提高。我这里宁愿让页面晚一点进入 full,也不希望右侧面板刚出现就把主卡片挤窄。对整理结果页来说,主卡片仍然是主要区域,辅助面板只是补充。

这个判断也可以迁移到其他页面里。比如待办页、提醒页、识别结果页,只要右侧有辅助信息,进入 full 的阈值都要看主内容还能不能保住。小窗里最容易出问题的地方,不是信息少了,而是主任务被辅助信息挤到后面。

2.2 compact 保留摘要和主操作

窗口缩到中等宽度时,页面进入 compact。这个状态还可以保留摘要和次要按钮,但不再显示完整识别内容和右侧辅助面板。

示例里 compact 的门槛是:

private readonly compactWidth: number = 620;

这个范围适合中等宽度窗口。用户还能看到标题、状态和一小段摘要,也能点主按钮和次按钮。它不像 full 那样展示完整上下文,但也没有马上退到只剩一个按钮。

我不会把所有内容一次性隐藏。窗口从宽到窄时,信息可以逐步收起来。先收右侧辅助面板,再收完整识别内容,最后才进入 minimal。这种逐步收缩的方式更容易迁移到真实项目,因为你可以把每个字段放在哪个状态里,逐个判断清楚,而不是把小窗口状态单独做成另一套页面。

这里其实有一个很实际的开发经验。很多页面一开始做响应式布局时,会直接写两个版本,一个完整版本,一个小窗版本。短期看确实快,但字段一多,两个版本就会开始不一致。比如 full 里改了一个字段名,minimal 忘了同步;full 里加了一个状态,compact 里还没有。用状态控制同一张卡片的显示内容,会更容易维护。

2.3 minimal 只留下当前动作

当窗口继续缩小,页面进入 minimal。这个状态只保留标题、状态和主按钮,其他内容都先隐藏。

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

我没有给 minimal 再拆更多子状态。悬浮窗已经是一个很小的空间,如果再继续做很多细分,代码会变复杂,用户看到的变化也不一定有价值。更实际的处理方式,是给 minimal 一个明确边界。标题、状态、主操作留下,摘要、元信息、次操作、辅助面板收起。

这张三档状态图适合放在这里,因为它能把设计取舍一次性展示出来。full 不是默认状态,minimal 也不是低配状态,它们只是同一个页面在不同窗口宽度下承担不同任务。读者看完图,再看后面的代码判断,会更容易理解为什么要有 getLayoutMode() 这个函数。

在真实项目里,我会把这种状态拆分写成页面级规则,而不是分散到每个字段旁边。字段越来越多以后,只有先确定 full、compact、minimal 各自承担什么任务,后面加字段才不会破坏小窗口里的主操作。

三、显示规则要落到组件里

3.1 同一张卡片逐步收起内容

真正落到代码里,页面需要根据状态决定哪些内容显示。这个示例里,我没有把 fullcompactminimal 写成三套完全不同的页面,而是让同一张主卡片根据当前状态收缩内容。

摘要只在非 minimal 状态下显示:

if (!this.isMinimal()) {  Text('识别到物业费缴纳截止日期、金额明细和办理地点,建议保存为待办提醒。')}

完整识别内容和元信息只在 full 状态下显示:

if (this.isFull()) {  // 元信息  // 识别内容  // 右侧辅助面板}

这个写法的好处是状态关系比较集中。full 展示完整信息,compact 保留摘要和主操作,minimal 只保留处理入口。后续要改某个状态的显示内容,可以直接回到这些判断里调整。

真实项目里也适合按这个思路拆。字段本身不需要变,变化的是每个窗口状态下展示哪些字段。这样不会为了悬浮窗单独维护另一套数据结构,也不会让业务字段在多个页面里重复一遍。

我会把这个规则理解成“同一件事在不同窗口里显示不同层级”。用户处理的是同一条记录,状态也还是同一个状态,只是窗口变小以后,页面先把完成任务必须的信息放出来。这样写出来的代码,后面遇到分屏、自由窗口、悬浮窗时更容易统一。

3.2 主按钮要尽量提前出现

悬浮窗里最容易出问题的地方是主按钮。全屏里按钮放在内容下面没有太大问题,因为用户有足够空间从上往下读。小窗口里如果仍然把按钮排在一堆说明后面,用户就要先滚动再操作。

示例里,minimal 状态下按钮文案也会缩短:

Button(this.isMinimal() ? '处理' : '保存为待办提醒')

完整窗口里按钮可以写得完整一些,让用户知道动作含义;小窗口里按钮文字要短,卡片高度也要控制,不然主操作仍然会被压到下方。

这里的取舍很现实。悬浮窗里宁愿少放一点说明文字,也要让按钮直接出现在用户能看到的位置。完整说明可以等窗口变大后再展示,主动作不能一直藏在下面。下面这张图可以用来解释信息如何从完整卡片收缩到 minimal 卡片,读者看图时重点关注保留项,而不是关注视觉样式。

这张图也能帮助后面做项目迁移。你在真实项目里可以把每个页面的字段按这张图重新分层,标题、状态、主按钮放在最小集合里,摘要和元信息放在中间集合里,完整说明和辅助入口放在宽窗口里。这样写出来的页面,不会因为进入小窗就失去主任务。

四、直观感受运行结果

4.1 开启悬浮窗

页面能不能进入悬浮窗,不能只靠 ArkUI 里的布局代码。布局代码解决的是窗口变小以后页面怎么显示,工程配置解决的是应用是否允许进入分屏、悬浮窗这类窗口模式。这个地方如果没有提前配好,后面写再多 fullcompactminimal 判断,也只能在预览器里模拟宽度变化,没法真正验证小窗场景。

我一般会先打开 entry/src/main/module.json5,在 abilities 里的 EntryAbility 配置中确认 supportWindowMode。这个字段用来声明当前 UIAbility 支持哪些窗口模式,常用的三个值分别是 fullscreensplitfloating。其中 floating 对应自由悬浮窗口,split 对应分屏窗口,fullscreen 对应全屏窗口。这个字段应配置在 module.json5abilities 节点下。

可以参考下面这种写法,保留你项目里原有的 namesrcEntryiconlabel 等配置,只补充或检查 supportWindowMode 这一项:

{  "module": {    "abilities": [      {        "name": "EntryAbility",        "srcEntry": "./ets/entryability/EntryAbility.ets",        "description": "$string:EntryAbility_desc",        "icon": "$media:layered_image",        "label": "$string:EntryAbility_label",        "startWindowIcon": "$media:startIcon",        "startWindowBackground": "$color:start_window_background",        "exported": true,        "supportWindowMode": [          "fullscreen",          "split",          "floating"        ]      }    ]  }}

如果你的工程已经有 supportWindowMode,先不要整段替换,只检查里面有没有 floating。有些项目一开始只配了 fullscreen,页面全屏运行没有问题,但进入悬浮窗或分屏时就缺少入口。这里把 splitfloating 一起打开,后面测试分屏降级和悬浮窗极简界面时会方便一些。

我还会顺手检查 deviceTypes。如果这个页面本来就要覆盖 Pura X Max、平板、2in1 这类设备,模块里不要只保留 phone。常见写法会包含:

"deviceTypes": [  "phone",  "tablet",  "2in1"]

配置完成以后,再回到页面里处理 onAreaChange、窗口宽度判断和 minimal 状态。也就是说,module.json5 负责让应用具备进入悬浮窗的条件,页面代码负责在窗口真的变小以后,把标题、状态和主按钮保留下来。这样调试时就不会把工程配置问题误判成 ArkUI 布局问题。

4.2 查看运行结果

我这里提供了“完整”“紧凑”“极简”三个演示按钮,方便在同一台模拟器里观察窗口变窄后的变化。真实项目里不需要这些按钮,页面会根据实际窗口宽度自动切换。

我建议先分别看三种状态,因为悬浮窗适配真正要表达的是信息如何逐步收缩。完整模式里上下文更多,紧凑模式里开始减少辅助内容,极简模式里只保留当前任务所需的信息。

完整模式下,页面显示主卡片和右侧辅助面板,摘要、元信息、识别内容和多个操作入口都会出现。这个状态适合全屏或较宽窗口,用户可以看到完整上下文。

紧凑模式下,右侧辅助面板和完整识别内容被收起,主卡片仍然保留摘要、主按钮和少量次要操作。这个状态适合中等宽度窗口,它还允许用户了解大致内容,但不会把完整识别信息全部展示出来。

极简模式下,页面只剩一个小卡片。标题、状态和主按钮保留,摘要、元信息、次按钮、辅助面板都被隐藏。这个状态更接近悬浮窗里的临时处理界面,用户看到标题以后就可以直接点主按钮,不需要在小窗口里找入口。

五、如何实际放在自己项目中

5.1 演示宽度要删掉

代码里使用了 previewWidth 和顶部演示按钮,方便在同一个模拟器里观察 fullcompactminimal 三种状态。真实项目里不需要这些按钮,页面应该直接读取真实窗口宽度。

示例里的写法是:

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

迁回真实项目时,可以改成:

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

页面宽度仍然可以通过 onAreaChange 写入 pageWidth。这里记录的是页面区域宽度,不是设备型号,也不是设备方向。对悬浮窗来说,这个差别很重要,因为同一台设备上,窗口可以从完整展开态变成一个很小的浮动窗口。

5.2 minimal 不适合承载表单

minimal 状态适合快速处理,不适合复杂编辑。

像提醒确认、待办处理、计时暂停、整理结果保存,这些动作都可以用 minimal 承接。用户知道当前对象是什么,也能点主按钮完成处理。

如果页面是长表单、复杂编辑、图片裁剪、长文阅读,我不会强行塞进 minimal 状态。小窗口里可以保留标题和一个“打开完整页面”的入口,让用户进入完整页面继续处理。悬浮窗只是临时入口,不适合承担所有业务流程。

这个边界在真实项目里要提前想清楚。不是每个页面都应该有完整的 minimal 交互,有些页面在悬浮窗里只适合展示状态,有些页面可以提供一个快捷按钮,有些页面则应该提示用户回到完整窗口。适配不是把所有能力压缩到小窗里,而是判断小窗到底能承担哪一步。

5.3 辅助信息要有退路

摘要、来源、时间、标签、识别原文这些内容被隐藏后,不能就此消失。窗口变宽时,它们要重新出现;用户点击查看详情时,也应该能进入完整页面。

所以 minimal 不是删除信息,而是在小窗口里暂时收起。真实项目里,我会把字段分层写清楚:哪些字段在 minimal 显示,哪些字段在 compact 显示,哪些字段只在 full 或详情页里显示。这样后续加字段时,不会每次都把小窗口重新撑满。

我还会把这类规则尽量放到同一个页面配置里,而不是散落在每个组件的 if 分支里。字段越来越多以后,如果没有统一规则,最容易出现的情况就是有人在卡片里顺手加一个字段,小窗口高度又被撑起来,主按钮又被压到后面。

总结

悬浮窗里的页面不适合复刻全屏详情页。窗口缩到小尺寸以后,用户更可能是在临时处理一件事,而不是完整阅读所有信息。标题、状态和主按钮要留在最前面,摘要、元信息、识别内容和次级入口可以随着窗口变宽再出现。

我处理这类页面时,会把 fullcompactminimal 当成三个不同的信息层级。full 承载完整上下文,compact 保留摘要和主操作,minimal 只留下完成当前动作所需的内容。这样写不会把悬浮窗变成缩小版详情页,也不会让用户在小窗口里到处找按钮。

附:完整代码

interface MetaItem {  id: number;  label: string;  value: string;}​@Entry@Componentstruct Index {  // 页面真实宽度,由 onAreaChange 写入  @State private pageWidth: number = 0;​  // 演示宽度,只用于在同一个模拟器里切换 full / compact / minimal 三种状态  @State private previewWidth: number = 0;​  // 模拟操作次数,用来观察 minimal 状态下主操作是否正常保留  @State private doneCount: number = 0;​  // compactWidth 以下进入 minimal,fullWidth 以上显示完整内容和辅助面板  private readonly compactWidth: number = 620;  private readonly fullWidth: number = 840;​  private readonly metaItems: MetaItem[] = [    {      id: 1,      label: '来源',      value: '拍照整理'    },    {      id: 2,      label: '时间',      value: '09:20'    },    {      id: 3,      label: '类型',      value: '通知'    },    {      id: 4,      label: '优先级',      value: '高'    }  ];​  // 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.fullWidth) {      return 'full';    }​    if (width >= this.compactWidth) {      return 'compact';    }​    return 'minimal';  }​  private isFull(): boolean {    return this.getLayoutMode() === 'full';  }​  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.isFull()) {      return 24;    }​    if (this.isCompact()) {      return 16;    }​    return 10;  }​  private getTitleSize(): number {    if (this.isFull()) {      return 28;    }​    if (this.isCompact()) {      return 23;    }​    return 18;  }​  private getCardRadius(): number {    if (this.isMinimal()) {      return 16;    }​    return 22;  }​  private getModeText(): string {    if (this.isFull()) {      return 'full · 完整模式';    }​    if (this.isCompact()) {      return 'compact · 紧凑模式';    }​    return 'minimal · 最小可用';  }​  private getModeDesc(): string {    if (this.isFull()) {      return '完整窗口下展示摘要、元数据、识别内容和辅助面板。';    }​    if (this.isCompact()) {      return '中等窗口下保留摘要和主操作,收起完整识别内容。';    }​    return '小窗口下只保留标题、状态和主按钮。';  }​  private setPreview(width: number) {    this.previewWidth = width;  }​  private markDone() {    this.doneCount += 1;  }​  @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 StatusPill(text: string) {    Text(text)      .fontSize(12)      .fontColor('#B25E00')      .padding({ left: 8, right: 8, top: 4, bottom: 4 })      .backgroundColor('#FFF4E5')      .borderRadius(999)  }​  @Builder  private HeaderPanel() {    Column({ space: this.isMinimal() ? 6 : 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(13)            .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('完整', 920)          this.PreviewButton('紧凑', 680)          this.PreviewButton('极简', 360)        }        .width('100%')      } else {        Row({ space: 6 }) {          this.PreviewButton('自动', 0)          this.PreviewButton('完整', 920)          this.PreviewButton('极简', 360)        }        .width('100%')      }    }    .width('100%')  }​  @Builder  private MetaPill(text: string) {    Text(text)      .fontSize(12)      .fontColor('#4B5563')      .padding({ left: 8, right: 8, top: 4, bottom: 4 })      .backgroundColor('#F3F4F6')      .borderRadius(999)  }​  @Builder  private MetaRow(item: MetaItem) {    Row() {      Text(item.label)        .fontSize(13)        .fontColor('#9CA3AF')​      Blank()​      Text(item.value)        .fontSize(13)        .fontColor('#374151')        .fontWeight(FontWeight.Medium)        .maxLines(1)        .textOverflow({ overflow: TextOverflow.Ellipsis })    }    .width('100%')    .padding(12)    .backgroundColor('#F9FAFB')    .borderRadius(14)  }​  @Builder  private MainCard() {    Column({ space: this.isMinimal() ? 10 : 14 }) {      Row({ space: 8 }) {        this.StatusPill('待处理')​        if (!this.isMinimal()) {          this.MetaPill('通知')        }​        Blank()​        if (this.doneCount > 0) {          Text('已操作 ' + this.doneCount.toString() + ' 次')            .fontSize(12)            .fontColor('#2F8F83')        }      }      .width('100%')​      Text('社区物业缴费提醒')        .fontSize(this.isMinimal() ? 19 : 24)        .fontWeight(FontWeight.Bold)        .fontColor('#111827')        .lineHeight(this.isMinimal() ? 25 : 31)        .maxLines(this.isMinimal() ? 2 : 3)        .textOverflow({ overflow: TextOverflow.Ellipsis })​      // 摘要从 compact 开始显示,minimal 状态下先让主按钮出现在用户能看到的位置      if (!this.isMinimal()) {        Text('识别到物业费缴纳截止日期、金额明细和办理地点,建议保存为待办提醒。')          .fontSize(15)          .fontColor('#4B5563')          .lineHeight(23)          .maxLines(this.isCompact() ? 2 : 3)          .textOverflow({ overflow: TextOverflow.Ellipsis })      }​      // 完整识别内容只在 full 状态出现,避免小窗口承载过多阅读内容      if (this.isFull()) {        Column({ space: 8 }) {          ForEach(this.metaItems, (item: MetaItem) => {            this.MetaRow(item)          }, (item: MetaItem) => item.id.toString())        }        .width('100%')​        Column({ space: 8 }) {          Text('识别内容')            .fontSize(16)            .fontWeight(FontWeight.Medium)            .fontColor('#111827')​          Text('本期物业服务费缴纳截止日期为 2026 年 5 月 28 日。请在截止日期前完成缴费,避免影响后续服务办理。')            .fontSize(14)            .fontColor('#6B7280')            .lineHeight(22)        }        .width('100%')        .padding(14)        .backgroundColor('#F9FAFB')        .borderRadius(16)      }​      Button(this.isMinimal() ? '处理' : '保存为待办提醒')        .fontSize(this.isMinimal() ? 14 : 15)        .fontColor('#FFFFFF')        .height(this.isMinimal() ? 38 : 44)        .width('100%')        .backgroundColor('#2F8F83')        .borderRadius(this.isMinimal() ? 19 : 22)        .onClick(() => {          this.markDone();        })​      if (!this.isMinimal()) {        Row({ space: 10 }) {          Button('稍后处理')            .fontSize(14)            .fontColor('#2F8F83')            .height(40)            .layoutWeight(1)            .backgroundColor('#E6F4F1')            .borderRadius(20)​          Button('查看详情')            .fontSize(14)            .fontColor('#4B5563')            .height(40)            .layoutWeight(1)            .backgroundColor('#F3F4F6')            .borderRadius(20)        }        .width('100%')      }    }    .width('100%')    .padding(this.isMinimal() ? 12 : 18)    .backgroundColor('#FFFFFF')    .borderRadius(this.getCardRadius())    .shadow({      radius: this.isMinimal() ? 8 : 12,      color: '#12000000',      offsetX: 0,      offsetY: 4    })  }​  @Builder  private FullSidePanel() {    Column({ space: 12 }) {      Text('辅助内容')        .fontSize(17)        .fontWeight(FontWeight.Bold)        .fontColor('#111827')​      Text('完整窗口下保留辅助入口。进入小窗口后,这些内容会收起,把空间留给当前记录和主按钮。')        .fontSize(14)        .fontColor('#6B7280')        .lineHeight(22)​      Column({ space: 8 }) {        this.MetaRow({ id: 10, label: '建议', value: '截止前一天提醒' })        this.MetaRow({ id: 11, label: '关联', value: '日程 / 待办' })        this.MetaRow({ id: 12, label: '状态', value: '等待确认' })      }      .width('100%')​      Blank()​      Button('打开完整详情')        .fontSize(14)        .fontColor('#2F8F83')        .height(40)        .width('100%')        .backgroundColor('#E6F4F1')        .borderRadius(20)    }    .width('100%')    .height('100%')    .padding(18)    .backgroundColor('#FFFFFF')    .borderRadius(24)    .shadow({      radius: 12,      color: '#12000000',      offsetX: 0,      offsetY: 4    })  }​  @Builder  private MainContent() {    if (this.isFull()) {      Row({ space: 16 }) {        Column() {          this.MainCard()        }        .layoutWeight(1)​        Column() {          this.FullSidePanel()        }        .width(280)      }      .width('100%')    } else {      Column({ space: 12 }) {        this.MainCard()​        if (this.isCompact()) {          Text('当前窗口保留摘要和主操作,完整识别内容和辅助面板暂时收起。')            .fontSize(13)            .fontColor('#6B7280')            .lineHeight(20)            .padding({ left: 4, right: 4 })        }      }      .width('100%')    }  }​  build() {    Column() {      Column({ space: this.isMinimal() ? 10 : 16 }) {        this.HeaderPanel()        this.MainContent()      }      .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)    .justifyContent(this.isMinimal() ? FlexAlign.Center : FlexAlign.Start)    .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 优秀布道师、科大讯飞荣誉讲师。

12

帖子

0

提问

25

粉丝

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

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255