鸿蒙原生应用实战:AI PPT 创建页的大纲卡片联动与生成交互
App 26「AI PPT 生成」创建 Tab(Func1Tab),主题色 #EA580C 暖橙(orange)。创建页是系列第一个"AI 生成型交互"——输入卡片(主题 + 补充要求)+ 生成设置(页数 8/10/12/15/20 单选)+「🤖 AI 生成大纲」按钮 + @State generating 控制的「AI 生成大纲」卡片联动(4 章 12 要点 + 绿色「生成 PPT」按钮)。本篇基于 26-ai-ppt/entry/src/main/ets/pages/Func1Tab.ets(约 134 行)逐段拆解,附 4 张实机截图。

一、整体结构:4 块"生成流程"布局
创建页是"输入 → 设置 → 生成 → 出稿"的线性流程——4 个 @Builder 块:
build() {
Column() {
this.Header()
Scroll() {
Column({ space: 16 }) {
this.InputCard()
this.SettingCard()
this.GenerateBtn()
if (this.generating) {
this.OutlineCard()
}
}
.width('100%')
.padding({ left: D.pad, right: D.pad, top: 16, bottom: D.pad + this.safeBottom + 20 })
}
.layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
}
.width('100%').height('100%').backgroundColor(C.bg)
}
5 块结构:
if (this.generating) { this.OutlineCard() }——"条件渲染 = 生成状态驱动 UI 出现"——这是创建页的交互灵魂:没点按钮前只有"输入 + 设置 + 按钮",点了按钮大纲卡才出现。
为什么用 if 而不是隐藏/显示(visibility)?——"if 是真正地添加/移除节点":if 控制下,未生成时大纲卡完全不存在(不占布局、不渲染),生成后整块插入——"状态驱动渲染,是声明式 UI 的推荐做法"——比 visibility 切换更省资源、代码更直观。
生成前后页面状态对比:
- 生成前:输入卡(主题/要求)+ 设置卡(页数)+ 暖橙按钮——"全是表单"
- 生成后:追加大纲卡(4 章 12 要点 + 绿色按钮)——"出现结果"
"表单 → 结果"的状态翻转——"AI 生成类页面 = 表单 + 条件渲染的结果区"——用户先填、后看结果,结果是"生成"出来的而不是"写"出来的——"生成感 = 结果区按状态出现"。
与 App 21 在线问卷(qType 联动)同构——"条件渲染":App 21 按题型换表单、App 26 按生成状态显大纲——"同一系列里条件渲染玩出了不同花样"。
space: 16 比首页的 14 更大——"表单页间距稍大,输入更透气"——表单/设置页需要比列表页更宽松的呼吸感——"间距密度 = 页面类型的视觉语言"。
主题色 #EA580C 暖橙——生成按钮用暖橙底白字,选中页数用暖橙高亮——"创建 = 生成动作,用最亮的橙色强化行动"。
创建页的 Tab 身份:底部 Tab 用 🤖 图标、"创建"标题——"🤖 是 AI 生成的动作符号"——首页 🖼️(看素材)→ 创建 🤖(做新 PPT)——"Tab emoji 就是该页动词"。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub
二、InputCard:主题 + 补充要求
InputCard 是"输入主题 + 补充要求"的输入卡片:
@Builder InputCard() {
Column({ space: 12 }) {
Text('输入主题').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
TextInput({ placeholder: '例如:人工智能在医疗领域的应用' })
.height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm)
.placeholderColor(C.textDim).placeholderFont({ size: 14 })
TextArea({ placeholder: '补充要求:风格、受众、重点章节…' })
.height(70).backgroundColor(C.cardSoft).borderRadius(D.rSm)
.placeholderColor(C.textDim).placeholderFont({ size: 14 })
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}
两级输入设计:
- TextInput(44 高)——"主题"——placeholder: '例如:人工智能在医疗领域的应用'——"示例式占位文案"(不只写"请输入主题",而是给一个完整示例,降低用户构思成本)
- TextArea(70 高)——"补充要求"——placeholder: '风格、受众、重点章节…'——"字段枚举式占位"(三个逗号分隔的词告诉你"可以补什么")
占位文案的两种写法是本页细节亮点:
为什么占位文案要"手把手"?——"AI 生成的结果质量取决于 prompt 质量":用户输入越具体,生成越精准——"占位文案 = 在教用户怎么写好 prompt"——这是 AI 类产品 UI 的独特要求:"表单不只是收集输入,还在引导表达"。
输入层级:主题是必填核心(决定 PPT 讲什么),补充要求是可选增强(决定 PPT 怎么讲)——"一主一辅、一短一长"的输入架构——短输入(44 高单行)+ 长输入(70 高多行)视觉上也分出主次。
placeholderFont({ size: 14 })——占位文字 14 号——与后续大纲正文同级——"占位即输入后的样子"——减少输入前后的视觉落差——"占位字号的细节 = 输入体验的顺滑度"。
TextInput vs TextArea 的选择:单行(主题短)用 TextInput、多行(要求长)用 TextArea——"控件形态 = 内容形态"——一行装得下用单行,装不下用多行——"表单控件选型的标准"。
卡片样式:白底 + rLg 大圆角 + 细边框——与首页卡一致——"输入卡与展示卡同款容器"——"卡片是系列的基础容器"。
三、SettingCard:页数单选(8/10/12/15/20)
SettingCard 是"页数设置"卡片——当前值展示 + 5 档单选:
@Builder SettingCard() {
Column({ space: 12 }) {
Text('生成设置').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Row() {
Text('页数').fontSize(13).fontColor(C.textSub)
Blank()
Text(this.pages + ' 页').fontSize(13).fontColor(C.primary).fontWeight(FontWeight.Bold)
}.width('100%')
Row({ space: 8 }) {
ForEach([8, 10, 12, 15, 20], (n: number) => {
Text(n.toString())
.fontSize(13)
.fontColor(this.pages === n ? '#FFFFFF' : C.textSub)
.width(40).height(36).textAlign(TextAlign.Center)
.backgroundColor(this.pages === n ? C.primary : C.cardSoft).borderRadius(D.rSm)
.onClick(() => { this.pages = n; })
}, (n: number) => n.toString())
}.width('100%')
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}
设置卡结构:
- 标题行:左"生成设置"(15 号加粗)、中间 Blank() 撑开、右"12 页"(暖橙加粗)——Blank() = ArkUI 的弹性占位,让"页数"和"当前值"两端对齐
- ForEach([8, 10, 12, 15, 20], …)——5 档页数选项(8/10/12/15/20,覆盖短篇到长篇)
5 档页数的选择逻辑:8(精简)/10(标准)/12(主流)/15(详尽)/20(超长)——"覆盖从 5 分钟演讲到 30 分钟汇报的 PPT 长度谱系"——"选项要覆盖完整的使用区间"——既照顾快速出稿、又支持深度内容。
选中逻辑:this.pages === n ? 橙底白字 : 浅灰底灰字——"全等比较 + 三态样式"——@State pages: number = 12 默认 12 页——"12 是主流 PPT 的中间档"(demo 默认值有讲究)——"默认值 = 多数用户的选择,减少操作"。
@State pages 双向呈现:
"一处状态、两处展示"——@State 的响应式让"标题值"和"选项高亮"自动同步——**"改一处、两处变"**是声明式 UI 的核心体验。
Blank() 的妙用——ArkUI 的 Blank 组件会自动占据剩余空间(类似 Flutter 的 Spacer)——"标题行两端对齐,中间自动弹性"——不用手算宽度,"声明式布局的'自动'是开发效率的来源"。
选项按钮的尺寸:width(40).height(36) 固定尺寸 + textAlign(TextAlign.Center) 居中——"数字选项是等宽方块,比文字按钮更整齐"——"数字选择用方块、文字选择用胶囊"——形态语义区分。
选项的圆角 rSm(10)——比选中胶囊(16)更小——"小圆角 = 小控件"——"圆角半径跟控件尺寸走"——层级一致。

四、GenerateBtn:触发生成
GenerateBtn 是全宽"🤖 AI 生成大纲"按钮:
@Builder GenerateBtn() {
Button('🤖 AI 生成大纲')
.width('100%').height(48)
.fontSize(16).fontColor('#FFFFFF').backgroundColor(C.primary)
.borderRadius(D.rMd)
.onClick(() => {
this.generating = true;
promptAction.showToast({ message: '生成中…' });
})
}
按钮设计:
- 文案"🤖 AI 生成大纲"——"AI + 生成 + 大纲"三要素齐全——"告诉用户按下后会发生什么"
- 暖橙底 + 白字 + 48 高 + 圆角——"全宽主按钮"——创建页的视觉重心
- 48 高——"拇指热区"(最小触控高度规范),比页面其他 44/38 按钮更突出
为什么是全宽按钮?——"生成动作是页面的唯一主线":全宽按钮横贯屏幕,视觉上"拦住"整个表单——"全宽主按钮 = 告诉用户'填完就点这里'"——"主行动按钮要'一屏只有一个'"——页面上暖橙只出现在主按钮和选中态,焦点不分散。
点击行为(两件事):
"状态翻转 + Toast"的生成模拟——demo 里没有真实 AI 请求,但**"点了 → 有反应"的交互闭环完整——"生成中…"Toast 让用户感知到'AI 正在干活'**——这是 AI 类 demo 的通用模拟手法(App 26 用 generating 布尔值驱动)。
为什么 Toast 文案用"生成中…"而不用"生成成功"?——"生成中"是进行时态:告诉用户"开始干活了",紧接着大纲卡就出现——"Toast 语义要和 UI 变化呼应"——如果弹"成功"但结果没立刻显示,反而突兀——"先'进行中'、后'结果出现',时序自然"。
生成前的完整流程:填主题(可选项)→ 选页数(可选)→ 点按钮 → 出大纲——"三步生成"——**"输入最少、路径最短"**的 AI 生成交互模型。
交互模型的简化逻辑:真实 AI PPT 产品会要"主题 + 受众 + 风格 + 语气 + 配色"一堆参数,demo 压到"主题(可不填)+ 页数"两项——"demo 砍参数、保流程"——"最小可用的生成流程 = 主题 + 一个关键参数"——页数是最影响结果形态的参数,保留它最有代表性。
五、OutlineCard:AI 生成大纲(本页核心)
OutlineCard 是 4 章 12 要点的结构化大纲卡 + 绿色「生成 PPT」按钮:
@Builder OutlineCard() {
Column({ space: 14 }) {
Text('AI 生成大纲').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).width('100%')
ForEach(this.outlines, (o: OutlineItem) => {
Column({ space: 6 }) {
Row({ space: 8 }) {
Text(o.id.toString()).fontSize(14).fontWeight(FontWeight.Bold).fontColor(C.primary)
Text(o.title).fontSize(14).fontColor(C.text)
}.width('100%')
ForEach(o.points, (p: string, idx: number) => {
Text(' · ' + p).fontSize(12).fontColor(C.textDim).width('100%')
}, (p: string, idx: number) => p + idx)
}
.width('100%')
}, (o: OutlineItem) => o.id.toString())
Button('生成 PPT')
.width('100%').height(44)
.fontSize(14).fontColor('#FFFFFF').backgroundColor(C.ok)
.borderRadius(D.rMd)
.onClick(() => { promptAction.showToast({ message: 'PPT 生成成功' }); })
}
.width('100%').padding(16).backgroundColor(C.card).borderRadius(D.rLg).border({ width: 1, color: C.stroke })
}
4 章大纲(每章 3 要点):
大纲卡结构(两级渲染):
- 外层 ForEach(this.outlines)——渲染 4 章——"章节号(暖橙加粗)+ 章节名"
- 内层 ForEach(o.points)——渲染每章 3 个要点——"' · '前缀 + 要点(12 号灰字)"
"章节 + 要点"的树形大纲——"1→2→3→4 的叙事逻辑":背景(为什么做)→ 方案(怎么做)→ 计划(何时做)→ 成果(做出什么)——"一份标准商业 PPT 的四段式骨架"——demo 的模拟大纲内容专业、可用。
为什么大纲要点每条都带" · "前缀?——"无 list-style 的文本列表,用字符模拟项目符号"——ArkUI 的 Text 组件没有原生列表符号,demo 用空格 + 中点号拼出缩进感——"用纯文本实现列表 = 零依赖的轻量方案"——与 emoji 代替图片同一个思路。
四级结构分析:项目背景(市场/痛点/目标)→ 技术方案(架构/技术/创新)→ 实施计划(里程碑/资源/风险)→ 预期成果(量化/价值/展望)——"从'为什么'到'怎么做'到'何时做'到'做出什么'的完整商业叙事"——"大纲的章节目录本身就是一份可交付的文档"——这体现了 demo 内容设计的用心。
Text(o.id.toString()) 暖橙章节号——"1/2/3/4"大号加粗——"序号 = 视觉锚点"——扫读大纲时先看序号,再读内容——"序号暖橙、章节名深色、要点灰色"——"三级灰度 = 大纲的视觉层级"。
ForEach(o.points, (p, idx) => p + idx) 的 key——要点内容 + 索引拼 key——"内容可能重复,加索引保证唯一"——ForEach key 的唯一性处理细节——"同内容要点用 '内容+索引' 做 key,避免渲染冲突"。
绿色「生成 PPT」按钮(C.ok #2BB673)——"第二级行动"——暖橙按钮管"生成大纲"(第一级)、绿色按钮管"生成 PPT"(第二级)——"两个动作两种色"——用颜色区分流程阶段,避免视觉混淆。
为什么第二级用绿色?——"绿色 = 完成/可继续":大纲已生成,下一步"生成 PPT"是正向推进——"绿色按钮语义 = '继续、做成'"——"按钮色 = 动作语义"(橙=行动、绿=完成、灰=次级)。
生成 PPT 按钮文案与 Toast:"生成 PPT" → 弹"PPT 生成成功"——"从大纲到成品的第二跳"——demo 用 Toast 模拟"PPT 已生成"——"流程终点 = 成功反馈"——生成类产品的"闭环感"。
点击后滚动查看:大纲卡出现在按钮下方,页面自动可滚——"生成的大纲"是用户当前最关心的内容,"新内容出现在视线下方、滚动即见"——符合"点击 → 看结果"的心智。
为什么大纲卡不自动滚到视野?——"保留上下文":用户生成大纲后可能还想改页数(设置卡在上方),如果强制滚动到底会打断"调整参数"的路径——"不强制滚动 = 让用户自己决定看哪"——"克制比打扰好"。
状态机的一次性:点击一次后 generating = true,大纲卡常驻——"再点按钮只是再弹 Toast"——demo 的简化:"生成一次即可,重复生成不更新大纲"——真实产品会替换/刷新大纲——"demo 保留最核心的'出现',不做复杂的状态轮换"。

六、数据模型与条件渲染逻辑
OutlineItem 模型(3 字段):
- id: number — 章节号(ForEach key + 序号展示)
- title: string — 章节名
- points: string[] — 要点数组(数组嵌套 = 树形大纲的数据结构)
双 ForEach 渲染树:外层章节 + 内层要点——"数据两级的、渲染两级"——ArkUI 的 ForEach 天然支持嵌套数据——"树形数据 → 嵌套 ForEach → 结构化展示"——这是 ArkUI 处理多级列表的标准范式。
generating 状态机:
- false(初始)→ 只显示输入/设置/按钮
- true(点击生成后)→ 追加显示大纲卡
@State 响应式驱动:改 generating → build 重跑 → if 分支重新计算 → UI 出现/消失——"一个布尔值控制一整块 UI 的生灭"是声明式 UI 最典型的用法——"状态是 UI 的唯一真相源"。
状态驱动的心理模型:"点了生成 → 有东西出来"——用户不需要理解底层,但**"状态翻转带来的 UI 变化必须即时、符合预期"——@State 的自动刷新保证了这一点——"声明式 UI 的价值 = 不用手写刷新逻辑"**。
主题呼应:主按钮暖橙(行动)、次按钮绿(完成)、选中页数暖橙(状态)、章节号暖橙(强调)——"暖橙贯穿创建的每个交互节点"——绿只出现一次(生成 PPT),语义独特、不抢戏——"功能色要克制使用"。
与首页的呼应:首页模板卡 4 色(橙/绿/浅橙/金)、创建页按钮 2 色(橙/绿)——"同一个色板在首页做展示、在创建页做功能"——"色板的复用设计"。
七、生成页的状态管理与交互设计
7.1 @State 声明式状态的运转
创建页只有两个状态:
@State generating: boolean = false;
@State pages: number = 12;
@State 的响应式原理:@State 修饰的变量被 ArkUI 的响应式系统追踪,变量一变,build() 自动重跑——"你不用手动刷新 UI"——这是声明式 UI 与传统命令式(setText/notifyDataChanged)的本质区别。
两个状态的职责:
- generating——"流程状态"(控制大纲卡出现)
- pages——"参数状态"(控制页数选择与展示)
"流程状态 + 参数状态"的分工——生成页天然有两类状态:"进行到哪一步"(generating)+"用什么参数"(pages)——"把状态按职责分类,代码更清晰"。
7.2 状态的最小化原则
创建页只用了 2 个状态,是**"状态最小化"**的示范:
- 主题/要求输入——不需要状态(demo 不消费输入值,用 TextInput 默认值即可)
- 大纲数据——硬编码 private outlines(不随用户输入变化)
"哪些需要 @State,哪些不需要"——"只有当 UI 需要随数据变化时,才用 @State"——demo 里大纲是写死的,不需要状态;页数是用户可点的,必须状态——"状态 = 交互的需求,不是数据的存在"。
如果真实化:接真实 AI 接口时,outlines 会从"写死的 private"变成"接口返回的 @State"——"demo 的 private 写死 → 真实化的唯一改动是加 @State"——"demo 把状态边界画得很清楚,真实化成本低"。
7.3 Button 与输入组件的细节
Button 的三个实例:
- 🤖 AI 生成大纲:全宽 48 高、暖橙、16 号——主行动
- 生成 PPT:全宽 44 高、绿色、14 号——次级行动
- 3 个编辑按钮(在编辑页):38 高、灰底——微操
"同一组件三种形态"——Button 的形态差异完全由参数决定(高/色/字号/圆角)——"用参数控制形态,是 ArkUI 组件的通用写法"——"组件复用 = 参数化"。
TextInput/TextArea 的 placeholder 设计——两级占位(示例式 + 维度式)——"占位文案 = 表单的灵魂"——"写占位文案的时间不该省,它是用户的第一位老师"。
7.4 创建页的可用性分析
生成流程的可用性检查:
"四项可用性检查"是评估交互页面的标准——demo 满足了前 3 项,第 4 项留给真实产品——"demo 的取舍:保证主路径顺畅,扩展能力留白"。
7.5 创建页与 AI 真实产品的差距
真实 AI PPT 产品(Gamma/美图 AI PPT)的创建流程:主题 → 风格 → 受众 → 语气 → 配色 → 参考图——demo 压到"主题 + 页数"两项——"demo 砍参数、保流程":
砍掉的参数去哪了?——TextArea placeholder: '风格、受众、重点章节…' 把"风格/受众"并进了补充要求——"用自由文本收纳被砍的参数"——"demo 的参数收敛技巧:多参数 → 一栏自由文本"——这是 demo 设计的智慧:"保留流程完整性,参数用最轻的方式承载"。
页数为什么保留为单选?——"页数是影响结果形态最直接的参数"(10 页和 20 页是完全不同的 PPT)——"保留对结果影响最大的参数,砍掉锦上添花的参数"——"参数保留的优先级 = 对结果的影响度"。
7.6 页面滚动与内容层级
创建页内容(输入 44+70 + 设置 + 按钮 + 大纲 4 章)超出首屏,Scroll 整页滚动——"生成结果(大纲)在下方,滚动可见"——"长表单页的滚动设计:核心输入尽量靠上,结果区跟随其后"。
首屏可见内容分析:Header + 输入卡 + 设置卡 + 生成按钮——"首屏装下'完整输入界面'"——用户不用滚动就能填完参数并点击生成——"首屏 = 主操作路径完整可见"——生成按钮在首屏底部,是精心计算的结果。
7.7 创建页的细节清单
- Blank() 两端对齐——标题行"页数"与"12 页"自动两端分布
- textAlign(TextAlign.Center)——数字选项方块文字居中
- placeholderFont({ size: 14 })——占位与正文同字号,输入前后不跳动
- borderRadius 分级——按钮 rMd、选项 rSm、卡片 rLg——"圆角与控件尺寸匹配"
- space: 16 区块间距——表单页比列表页更透气
"细节清单 = 质量清单"——这些每个几行的小细节,加起来就是"这个页面好用"的体感——"UI 的质量藏在细节里"。
八、总结:AI 生成流程的创建页设计
App 26 创建页一句话:用暖橙(#EA580C)组织一条"输入 → 设置 → 生成 → 出稿"的 AI 生成流水线——顶部单行 Header,中间输入卡片(主题 + 补充要求)与页数设置卡片,暖橙「🤖 AI 生成大纲」按钮触发 generating 状态,条件渲染出 4 章 12 要点的结构化大纲卡 + 绿色「生成 PPT」按钮。
3 个设计亮点:
生成类页面的通用范式:本页的结构(输入表单 + 设置参数 + 触发按钮 + 条件渲染结果)适用于任何"AI 生成"场景(AI 绘画/AI 写作/AI 视频)——"26 的创建页是可复制的生成器模板"。
与系列呼应:if 条件渲染同构 App 21(qType 联动)——"同一手法、不同场景";单行 Header(无副标题)同构 App 23/24 的功能页——"功能页 Header 从简,突出表单与按钮"。
体验细节:占位文案给示例、Blank() 弹性对齐、placeholderFont 统一字号、48 高拇指热区、Toast 全反馈——"AI 生成类的创建页要把'输入负担'降到最低"——demo 把生成模拟成"点一下就有结果",把 AI 产品最重的"等待感"藏在了"生成中…"Toast 里。
扩展性展望:generating 状态可扩展为"生成中(loading)→ 成功 → 失败"三态;pages 可联动真实生成请求的 slides 参数;outlines 可替换为接口返回的实时大纲——"demo 的三处状态/数据都有明确的真实化路径"。





