基于HarmonyOS API 24的幻影AI数字照相馆深度解析——紫色渐变主题、六Tab全差异化布局与AI写真生成闭环的完整实现
基于HarmonyOS API 24的幻影AI数字照相馆深度解析——紫色渐变主题、六Tab全差异化布局与AI写真生成闭环的完整实现
AI写真,一键生成你的专属大片。本文完整拆解一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式框架构建的「幻影AI」数字照相馆应用,覆盖紫色渐变主题配色系统、三色渐变头部、六Tab全差异化布局、AI写真大Banner、热门模板横滑、风格分类、精选作品、三列模板网格、四列相册网格、月度生成统计柱状图、创意工坊双渐变卡、会员权益卡、四动画状态联动、积分消耗闭环、Stack弹窗叠加、生成AI写真/修改订单/删除作品三大弹窗等核心技术点,附完整源码与运行效果截图。
一、引言:AI写真的数字化浪潮
在人工智能技术高速发展的今天,AI写真已经从传统的"去影楼拍照、等几天取片",演变为一种即时化、个性化、大众化的数字消费新形态。AI写真,以其"无需出门、一键生成、风格多样、成本低廉"的特点,正在成为数字内容创作的关键赛道。从古风汉服到赛博朋克,从日系动漫到油画肖像,从复古港风到婚纱大片,每一种风格都能满足用户对不同自我形象的探索与表达。然而,模板质量参差不齐、生成效果不可控、积分消耗不透明、作品管理混乱,正成为用户获取可靠AI写真服务的现实痛点。
正是在这样的背景下,幻影AI类数字照相馆应用应运而生,并迅速成为AI内容创作的数字基础设施。一个优秀的AI写真应用,不仅需要覆盖从"选模板"到"生成"再到"管理"的完整业务闭环,还要在视觉设计、交互体验、数据呈现上做到科技而浪漫,让用户在指尖滑动间便能完成选模板、传照片、看进度、管作品的全流程。
本文将深入剖析一个基于鸿蒙 HarmonyOS API 24 与 ArkUI 声明式开发框架构建的幻影AI数字照相馆应用。该应用以"首页"为核心,横向扩展出写真、相册、订单、工坊、我的六大功能模块,每个模块采用完全不同的布局风格——首页大Banner+热门横滑+风格分类+精选作品、写真三列网格+全部模板列表、相册四列网格+收藏作品、订单月度柱状图+订单列表、工坊双渐变卡+创意工具列表、我的用户卡+统计+会员权益+菜单,形成了一个完整闭环的AI写真生态。它不仅是一个功能完备的演示项目,更是一份典型的 ArkUI 工程实践范本,涵盖了状态管理、组件化设计、Stack弹窗叠加、四动画状态联动、紫色渐变视觉语言、积分消耗闭环、月度柱状图三档着色等关键技术点。

二、应用整体架构概览
整个应用采用了"单入口 + 六Tab全差异化布局 + Stack弹窗叠加"的AI内容创作应用架构。入口组件 AIPhotoStudioApp 作为整个应用的根容器,负责管理当前激活的 Tab 状态、积分余额、四个动画状态以及三个弹窗的显隐状态。
应用一共划分为六个一级页面:首页(HOME)、写真(PHOTO)、相册(ALBUM)、订单(ORDER)、工坊(WORKSHOP)以及我的(PROFILE)。与之前项目"页面布局风格统一或半差异化"不同,本应用的六个 Tab 每个布局完全不同——首页采用大Banner+热门横滑+风格分类+精选作品、写真采用三列网格+全部模板列表、相册采用四列网格+收藏作品、订单采用月度柱状图+订单列表、工坊采用双渐变卡+创意工具列表、我的采用用户卡+统计+会员权益+菜单,这种"一页一型"的差异化设计最大程度还原了真实AI写真App的交互体验。底部导航采用单排六Tab布局,激活Tab有紫色下划线指示器,简洁统一。
在数据层面,应用定义了一套完整的类型系统,包括色彩主题、写真模板元数据、用户作品元数据、订单元数据、创意工具元数据、月度统计接口等,并通过常量数组将数据与 UI 表现层解耦。所有业务数据以 @Observed 装饰的可观察类为载体(PhotoTemplate/UserPhoto/OrderInfo/CreativeTemplate),配合 10 个写真模板、12 个用户作品、10 条订单、8 个创意工具的模拟数据,模拟了一个真实的AI写真数据环境。所有数据类都带有 constructor 构造函数,使用 new PhotoTemplate(...) 方式实例化。MonthStat 采用普通 interface 接口而非 @Observed 类,因为月度数据是静态统计数据无需响应式更新。
在交互层面,应用通过 Stack 布局叠加实现了三类弹窗的显示:生成AI写真、修改订单、删除作品。与之前项目"每个弹窗独立 Stack 叠加"不同,本应用独创了 modalOverlay 统一封装——一个 @Builder modalOverlay(onClose) 构建器,内部通过 if-else 判断当前显示哪个弹窗内容,外层统一全屏 rgba(0,0,0,0.4) 遮罩 + 居中 90% 宽卡片 maxHeight 80%,点击遮罩同时关闭所有三个弹窗状态。这种统一封装大幅减少了重复代码,三个弹窗共享同一套遮罩与卡片样式。生成AI写真弹窗确认后 credits -= selectedTemplate.price(积分消耗闭环),修改订单弹窗展示订单信息只读,删除作品弹窗二次确认后关闭,形成了完整的交互闭环。
在视觉层面,应用独创了"紫色渐变主题"设计——顶部 header 使用 linearGradient 90 度从 primaryDark #4A148C 深紫黑 → primary #7B1FA2 深紫(0.5位置)→ accent #E040FB 亮紫品红的三色斜向渐变,"幻影AI"白色标题 + "AI数字照相馆"白色副标题 0.85 透明度,右侧积分胶囊"🎨 580"白字半透明白底圆角 16,是本应用最具辨识度的视觉元素。首页大Banner使用 linearGradient 135 度从 primaryDark 到 accent 的两色渐变,工坊两大卡片分别使用 135 度两色渐变(primary→accent / primaryDark→primary),会员权益卡使用 90 度两色渐变(primary→accent),形成了统一的紫色渐变视觉语言,贯穿整个应用。chart1-6 六种图表色全是紫色系(#7B1FA2/#E040FB/#9C27B0/#AB47BC/#CE93D8/#BA68C8),数据可视化统一紫色调。应用还独创了"四动画状态联动"设计——aboutToAppear 中 setInterval 1500ms 同步切换四个布尔动画状态(breathAnim/blinkAnim/pulseAnim/floatAnim),分别用于不同元素:blinkAnim 用于收藏作品星星缩放和订单生成中状态缩放、pulseAnim 用于热门模板tag/全部模板tag/创意工具按钮/VIP标签缩放、floatAnim 用于精选作品收藏星星缩放和月度柱状图缩放,四个动画同步切换形成整体协调的联动动效体系。应用还独创了"积分消耗闭环"设计——header 显示积分胶囊,生成AI写真弹窗确认后 credits -= price,我的页显示积分,形成积分获取-消耗-展示的完整闭环。

下面逐页展示应用的六大功能模块运行效果。
三、写真页:三列模板网格与全部模板列表
写真页以"AI写真模板"为核心,独创"三列网格+全部模板列表"的双层布局。

页面顶部第一部分是标题"📸 AI写真模板"加粗 16 号。下方第二部分是三列模板网格,使用三排 Row 布局,每排三个模板(slice(0,3)/slice(3,6)/slice(6,9)),每个模板卡片 layoutWeight 1 均分宽度,padding 上下 14,白底圆角 12,margin 右 8,包含:图标 36 号 margin 下 4 + 模板名称(11 号 maxLines 1 省略)+ 价格(primary 深紫 12 号加粗)。三列网格让用户快速浏览模板缩略图和价格,点击卡片触发 generateModal 生成AI写真弹窗,selectedTemplate 记录当前模板。
下方第三部分是全部模板列表,标题"📋 全部模板"加粗 16 号 margin 下 12。遍历 10 个模板,每个模板横向列表布局,padding 12,白底圆角 12,margin 下 8,包含:
左侧:图标 32 号 margin 右 12 + 模板信息列(layoutWeight 1,左对齐):模板名称(加粗 14 号)+ 标签行(风格标签 tagBg 紫底 tagText 深紫字圆角 4 + tag 标签 primary 紫底白字圆角 4 margin 左 4,tag 标签带 pulseAnim 缩放动画 this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 } duration 600ms)+ 评分行(★评分 warning 橙 10 号 + 使用人数 textSub 灰 10 号 margin 左 6)。
右侧:价格按钮列(右对齐):价格(primary 深紫 15 号加粗)+ 生成按钮(56×26 primary 紫底圆角 13 margin 上 6,点击触发 generateModal)。
10 个模板覆盖十大风格:古风汉服写真(国风,¥99,4.9分,3580人用,热门)、赛博朋克霓虹(科幻,¥129,4.8分,2890人用,新品)、日系动漫二次元(二次元,¥89,4.9分,4520人用,热销)、油画肖像风格(艺术,¥159,4.7分,1680人用,高端)、复古港风胶片(复古,¥109,4.6分,2340人用,经典)、水彩梦境少女(梦幻,¥119,4.8分,1950人用,推荐)、暗黑哥特风格(暗黑,¥139,4.5分,890人用,限定)、工笔花鸟国风(国风,¥99,4.6分,1230人用,新品)、婚纱写真大片(婚纱,¥199,4.9分,3120人用,热门)、黑白纪实摄影(纪实,¥89,4.4分,670人用,经典)。价格从 ¥89 到 ¥199,评分从 4.4 到 4.9,5 个 isHot 热门模板,完整还原了AI写真模板市场的多样性。全部模板列表的 tag 标签 pulseAnim 缩放动画让静态的模板列表有了"热门推荐"的动态感。
四、相册页:四列作品网格与收藏作品列表
相册页以"我的相册"为核心,独创"四列作品网格+收藏作品列表"的双层布局。

页面顶部第一部分是标题行"🖼 我的相册"加粗 16 号 layoutWeight 1 + "12张"textSub 灰 12 号(右对齐),margin 下 12。
下方第二部分是四列作品网格,使用三排 Row 布局,每排四个作品(slice(0,4)/slice(4,8)/slice(8,12)),每个作品卡片 layoutWeight 1 均分宽度,padding 上下 12,白底圆角 10,margin 右 6,包含:图标 28 号 margin 下 4 + 作品标题(9 号 maxLines 1 省略)+ ❤点赞数(danger 红 8 号 margin 上 2)。四列网格让用户快速浏览作品缩略图,第三排作品点击触发 deletePhotoModal 删除作品弹窗,selectedPhotoId 记录当前作品 ID。
下方第三部分是收藏作品列表,标题"⭐ 收藏作品"加粗 15 号 margin 下 12。遍历 this.photos.filter((p: UserPhoto) => p.isFavorite) 筛选出 4 个收藏作品,每个作品横向列表布局,padding 12,白底圆角 12,margin 下 8,包含:
左侧:图标 32 号 margin 右 12 + 作品信息列(layoutWeight 1,左对齐):作品标题(加粗 14 号)+ 模板名(textSub 灰 11 号 margin 上 2)+ 点赞行(❤点赞数 danger 红 10 号 + 日期 textSub 灰 10 号 margin 左 8)。
右侧:★星星(warning 橙 18 号,带 blinkAnim 缩放动画 this.blinkAnim ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 } duration 500ms)。
12 个作品从 08-19 到 08-08 按时间倒序排列,4 个收藏作品(紫禁之巅/樱花飘雪/梦境水彩/白色誓言/霓虹都市2,共5个?让我数一下:p1紫禁之巅true、p3樱花飘雪true、p6梦境水彩true、p9白色誓言true、p12霓虹都市2true,共5个收藏),点赞数从 67 到 489,模板覆盖十大风格,完整还原了用户的AI写真作品库。收藏作品的 ★星星 blinkAnim 缩放动画让收藏列表有了"闪烁"的动态感,是本应用动效设计的亮点之一。
五、订单页:月度生成统计柱状图与订单记录列表
订单页以"AI写真订单"为核心,独创"月度生成统计柱状图+订单记录列表"的复合布局。

页面顶部第一部分是月度生成统计柱状图卡片,标题"月度生成统计"加粗 15 号 margin 下 16。下方 Row 横向排列 6 个月柱子,每栏 layoutWeight 1 居中对齐,卡片 height 160,alignItems Bottom 让柱子从底部生长。每个柱子包含:
- 柱子本体:width 26,height = stat.count * 1.5(生成数乘以 1.5 作为像素高度),backgroundColor 按生成数分三档动态着色(count > 60 用 danger 红色:5月62/8月78;count > 40 用 primary 深紫:4月45/7月55;其余用 accentLight 浅品红:3月28/6月38),borderRadius 4,justifyContent End 让文字从底部对齐,padding 上 2,柱子内白字显示生成数(textWhite 9 号)。柱子带 floatAnim 缩放动画(
this.floatAnim ? { x: 1.03, y: 1.03 } : { x: 1.0, y: 1.0 }duration 600ms),营造出轻微的浮动感。 - 月份标签:stat.month(3月/4月/5月/6月/7月/8月)textSub 灰 9 号,margin 上 4。
6 个月数据从 3 月到 8 月,生成数从 28 到 78,平均评分从 4.5 到 4.9,5 月(62,五一假期)和 8 月(78,暑期)用红色突出显示,符合AI写真"假期和暑期高峰"的使用规律。三档着色(红/紫/浅品红)让生成量高低一目了然,柱子内白字显示数值避免了额外标注,floatAnim 缩放动画让静态柱状图有了轻微的呼吸感。
下方第二部分是订单记录列表,标题行"📋 订单记录"加粗 16 号 layoutWeight 1 + "共10单"textSub 灰 12 号(右对齐),margin 下 12。遍历 10 条订单,每条订单卡片布局,padding 12,白底圆角 12,margin 下 8,包含:
顶部行:订单号(textSub 灰 10 号 layoutWeight 1)+ 状态(动态着色:已完成 success 绿/生成中 warning 橙/已退款 danger 红,加粗 10 号,生成中状态带 blinkAnim 缩放动画 order.status === '生成中' && this.blinkAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 } duration 500ms)。
中部行:📸图标 28 号 margin 右 12 + 订单信息列(layoutWeight 1,左对齐):模板名(加粗 14 号)+ 数量 x count(textSub 灰 11 号 margin 上 4)+ 日期金额行(日期 textSub 灰 11 号 layoutWeight 1 + 金额 primary 深紫 15 号加粗,右对齐,margin 上 4)。
底部行:生成中订单显示"⏳ AI正在生成中…"(warning 橙 11 号 layoutWeight 1),已完成订单显示编辑按钮(primary 紫底 60×24 圆角 12 margin 左 8,点击触发 editOrderModal 修改订单弹窗,selectedOrder 记录当前订单),所有订单显示白底边框按钮(60×24 圆角 12)。
10 条订单从 08-19 到 08-10 按时间倒序排列,状态涵盖已完成(8 条)、生成中(1 条,油画肖像风格 08-16)、已退款(1 条,复古港风胶片 08-13),金额从 ¥89 到 ¥199,数量从 1 到 3,完整还原了用户的AI写真订单历史。生成中状态的 blinkAnim 缩放动画 + "⏳ AI正在生成中…"提示让用户一眼就能判断订单流转状态,是本应用交互设计的亮点之一。
六、工坊页:双渐变卡与创意工具列表
工坊页以"创意工坊"为核心,独创"双渐变卡+创意工具列表"的双层布局。

页面顶部第一部分是标题"🎨 创意工坊"加粗 16 号 margin 下 12。
下方第二部分是双渐变卡,使用 Row 横向布局,两张卡片各 layoutWeight 1:
左卡片"AI智能生成":Column 居中对齐,padding 上下 20,linearGradient 135 度从 primary #7B1FA2 深紫到 accent #E040FB 亮紫品红,圆角 16,包含:🤖图标 44 号 margin 下 8 + "AI智能生成"白字 15 号加粗 + "上传照片·一键写真"白字 11 号 0.85 透明度 margin 上 4 + 白色按钮 100×32 圆角 16 margin 上 12(点击触发 generateModal 生成AI写真弹窗)。
右卡片"批量处理":Column 居中对齐,padding 上下 20,linearGradient 135 度从 primaryDark #4A148C 深紫黑到 primary #7B1FA2 深紫,圆角 16,margin 左 10,包含:📊图标 44 号 margin 下 8 + "批量处理"白字 15 号加粗 + "多图·一键搞定"白字 11 号 0.85 透明度 margin 上 4 + 白色按钮 100×32 圆角 16 margin 上 12。
双渐变卡使用两种不同的紫色渐变(左卡 primary→accent 亮渐变,右卡 primaryDark→primary 深渐变),形成深浅对比,让创意工坊的两大核心功能一目了然。左卡"AI智能生成"是主要入口,使用更亮的渐变吸引用户点击。
下方第三部分是创意工具列表,标题"🛠 创意工具"加粗 15 号 margin 下 12。遍历 8 个创意工具,每个工具横向列表布局,padding 12,白底圆角 12,margin 下 8,包含:
左侧:图标 32 号 margin 右 12 + 工具信息列(layoutWeight 1,左对齐):工具名(加粗 14 号)+ 描述(textSub 灰 11 号 margin 上 2)+ 分类下载行(分类标签 tagBg 紫底 tagText 深紫字圆角 4 + 下载次数 textSub 灰 10 号 margin 左 6)。
右侧:圆形按钮(28×28 primary 紫底圆角 14,带 pulseAnim 缩放动画 this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 } duration 700ms)。
8 个创意工具覆盖四大类别:趣味(AI换脸8560次/AI漫画脸5670次/AI婚纱预览3450次/AI头像生成6780次)、修复(AI老照片修复6230次/AI黑白上色4890次)、编辑(AI背景替换7120次)、实用(AI证件照9340次,下载量最高)。下载量从 3450 到 9340,完整还原了AI创意工具的热度分布。创意工具按钮的 pulseAnim 缩放动画让静态的工具列表有了"可点击"的动态感,双渐变卡的深浅对比让工坊页视觉层次丰富。
七、我的页:用户卡、四栏统计、会员权益卡与菜单列表
我的页以"个人中心"为核心,采用"用户卡+四栏统计+会员权益卡+菜单列表"的四层布局。

页面顶部第一部分是用户卡,横向布局,padding 16,白底圆角 16,margin 下 16:Circle 头像 64×64 fill primaryLight 浅紫 + 用户信息列(layoutWeight 1,margin 左 12,左对齐):用户名"幻影用户"加粗 18 号 + 标签行(VIP 标签 primary 紫底白字圆角 8 padding 左右 6 上下 2,带 pulseAnim 缩放动画 this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 } duration 700ms + "积分 580"textSub 灰 10 号 margin 左 8,margin 上 4)。
第二部分是四栏统计卡片,横向排列四个数据指标,每栏 layoutWeight 1 居中对齐,padding 上下 16,白底圆角 16,margin 下 16:作品数 12(primary 深紫 22 号加粗)、订单数 10(accent 亮紫 22 号加粗)、积分 580(warning 橙 22 号加粗)、收藏 4(danger 红 22 号加粗),每个指标下方配文字标签(作品数/订单数/积分/收藏)textSub 灰 10 号 margin 上 2。四栏统计用四种颜色对应四种数据维度,一目了然。
第三部分是会员权益卡,Column 左对齐,padding 16,linearGradient 90 度从 primary #7B1FA2 深紫到 accent #E040FB 亮紫品红,圆角 16,margin 下 16:"👑 会员权益"白字 15 号加粗 + "每日无限生成 · 优先队列 · 专属模板"白字 11 号 0.85 透明度 margin 上 4。会员权益卡使用 90 度紫色渐变,与 header 的 90 度三色渐变呼应,形成统一的紫色渐变视觉语言。
第四部分是菜单列表,遍历 5 项(我的作品/积分商城/邀请好友/帮助反馈/关于我们),每项横向布局,padding 上下 14 左右 16,白底圆角 12,margin 下 8:功能名 text 14 号 layoutWeight 1 + ">"textSub 灰 14 号(右箭头)。菜单列表简洁明了,用户可点击进入对应功能页面。
我的页四层布局(用户卡+统计+会员权益+菜单)完整覆盖了个人中心的核心功能,VIP 标签的 pulseAnim 缩放动画让用户卡有了"会员尊贵"的动态感,会员权益卡的 90 度紫色渐变与 header 呼应,四栏统计的四种颜色让数据维度一目了然。
八、色彩主题系统:紫色渐变主题与六色紫系图表的科技浪漫视觉语言
8.1 色彩接口定义
任何一款设计精良的应用,背后都有一套严谨的色彩规范。该应用首先通过一个 ColorPalette 接口,将整个应用所需的色彩抽象为一个结构化的契约,共 24 个字段:
interface ColorPalette {
bg: string; cardBg: string;
primary: string; primaryLight: string; primaryDark: string;
accent: string; accentLight: string;
text: string; textSub: string; textWhite: string;
border: string; tagBg: string; tagText: string; shadow: string;
success: string; warning: string; danger: string;
chart1: string; chart2: string; chart3: string;
chart4: string; chart5: string; chart6: string;
}
- bg:页面整体背景色 #F3E5F5,紫白底,营造AI数字照相馆的科技浪漫氛围。
- primary:主色调 #7B1FA2,深紫,用于按钮、强调文字、激活Tab、价格、渐变头部中间色等高优先级视觉元素,是AI写真的标志性色彩(神秘、创意、高端)。
- primaryDark:深主色 #4A148C,深紫黑,用于渐变头部起始色、工坊右卡片渐变起始色,与 primary 形成同色系深浅对比。
- accent:辅助色 #E040FB,亮紫品红,用于渐变头部结束色、首页Banner渐变结束色、工坊左卡片渐变结束色、会员权益卡渐变结束色、订单数统计、收藏星星等,与深紫形成"深浅紫"的渐变组合,象征AI的科技感与创造力。
- text:文字主色 #1A1A2E,深蓝黑,用于主标题文字,与紫白底形成高对比度。
- shadow:阴影色 #D1C4E9,紫阴影,用于卡片阴影,营造柔和的紫色阴影效果。
- chart1-chart6:六种图表色全是紫色系(#7B1FA2/#E040FB/#9C27B0/#AB47BC/#CE93D8/#BA68C8),数据可视化统一紫色调,保证图表与整体主题的一致性。

8.2 色彩实例化
const COLORS: ColorPalette = {
bg: '#F3E5F5', cardBg: '#FFFFFF',
primary: '#7B1FA2', primaryLight: '#CE93D8', primaryDark: '#4A148C',
accent: '#E040FB', accentLight: '#EA80FC',
text: '#1A1A2E', textSub: '#9E9E9E', textWhite: '#FFFFFF',
border: '#E1BEE7', tagBg: '#F3E5F5', tagText: '#7B1FA2', shadow: '#D1C4E9',
success: '#4CAF50', warning: '#FF9800', danger: '#F44336',
chart1: '#7B1FA2', chart2: '#E040FB', chart3: '#9C27B0',
chart4: '#AB47BC', chart5: '#CE93D8', chart6: '#BA68C8',
};
主色 #7B1FA2 是一种神秘的深紫,既契合"AI数字照相馆"主题(神秘、创意、高端、科技的标志性色彩),又具有优雅、浪漫的气质。辅助色 #E040FB 亮紫品红与深紫形成"深浅紫"的渐变组合,是本应用最具辨识度的色彩组合。bg #F3E5F5 紫白底与主色同紫色系,营造出AI数字照相馆的科技浪漫氛围。
值得注意的是,本应用的渐变设计是色彩系统的核心亮点——header 使用 linearGradient 90 度从 primaryDark #4A148C 深紫黑 → primary #7B1FA2 深紫(0.5位置)→ accent #E040FB 亮紫品红的三色斜向渐变,是本应用最具辨识度的视觉元素;首页大Banner 使用 linearGradient 135 度从 primaryDark 到 accent 的两色渐变;工坊两大卡片分别使用 135 度两色渐变(左卡 primary→accent 亮渐变,右卡 primaryDark→primary 深渐变);会员权益卡使用 90 度两色渐变(primary→accent)。五种不同角度和配色的紫色渐变贯穿整个应用,形成了统一而丰富的紫色渐变视觉语言,是本应用色彩设计的核心特色。
本应用的动态颜色逻辑也十分丰富——月度柱状图按生成数分三档着色(>60 danger 红/>40 primary 深紫/其余 accentLight 浅品红),订单状态按状态分三档着色(已完成 success 绿/生成中 warning 橙/已退款 danger 红),风格分类分两排着色(第一排 primary 深紫/第二排 accent 亮紫),四栏统计分四色(作品数 primary/订单数 accent/积分 warning/收藏 danger),形成了"以色定量级、以色定状态、以色定分类"的多层语义着色模式。formatPrice 工具函数统一价格格式为 ¥XX.XX,价格统一使用 primary 深紫加粗,形成了一致的价格视觉语言。chart1-6 六种图表色全是紫色系,保证数据可视化与整体主题的一致性。这种将所有色彩集中管理、配合多维度渐变与动态着色的做法保证了全局一致性,降低了维护成本。

九、类型定义:构建严谨的数据契约
应用通过一组带 constructor 的 @Observed 类,为每一类业务对象建立了明确的数据契约。
9.1 写真模板、用户作品、订单元数据
@Observed
class PhotoTemplate {
id: string; name: string; style: string; price: number;
image: string; uses: number; rating: number; tag: string; isHot: boolean;
constructor(...) { ... }
}
@Observed
class UserPhoto {
id: string; title: string; template: string; date: string;
url: string; likes: number; isFavorite: boolean;
constructor(...) { ... }
}
@Observed
class OrderInfo {
id: string; orderNo: string; templateName: string; amount: number;
status: string; date: string; count: number;
constructor(...) { ... }
}
PhotoTemplate 描述写真模板的 ID、名称、风格、价格、图标、使用人数、评分、标签、是否热门,用 price 字段体现模板价格,uses/rating/isHot 三维度体现模板热度与品质。UserPhoto 描述用户作品的 ID、标题、模板名、日期、图标、点赞数、是否收藏,用 isFavorite 布尔字段体现收藏状态,likes 字段体现作品热度。OrderInfo 描述订单的 ID、订单号、模板名、金额、状态、日期、数量,用 status 字段体现订单状态(已完成/生成中/已退款),count 字段体现生成数量。
9.2 创意工具元数据与月度统计接口
@Observed
class CreativeTemplate {
id: string; name: string; desc: string; image: string;
category: string; downloads: number;
constructor(...) { ... }
}
interface MonthStat {
month: string; count: number; avgScore: number;
}
function formatPrice(price: number): string {
return '¥' + price.toFixed(2);
}
CreativeTemplate 描述创意工具的 ID、名称、描述、图标、分类、下载次数,用 category 字段体现工具分类(趣味/修复/编辑/实用),downloads 字段体现工具热度。MonthStat 采用普通 interface 接口而非 @Observed 类,包含月份、生成数、平均评分三个字段,因为月度数据是静态统计数据无需响应式更新,这种"按需选择 @Observed 或 interface"的设计体现了对响应式机制的精准理解,避免了不必要的响应式开销。formatPrice 工具函数统一价格格式为 ¥XX.XX,toFixed(2) 保证两位小数,是本应用唯一的工具函数。所有数据类都带有 constructor 构造函数,使用 new ClassName(...) 方式实例化,配合 @Observed 装饰器实现响应式更新。
十、配置驱动:常量数据与工具函数
10.1 Tab 导航配置
private tabItems: string[] = ['首页', '写真', '相册', '订单', '工坊', '我的'];
与之前项目"TabMeta 对象数组配置"不同,本应用的 Tab 导航采用最简的字符串数组配置,仅包含 Tab 名称,共 6 个 Tab。底部 tabBar 通过 ForEach 遍历 tabItems,每个 Tab 用 Column 包裹文字(11 号,激活态 primary 深紫加粗,非激活 textSub 灰),height 52,layoutWeight 1 均分,点击切换 currentTab。特别的是,激活 Tab 下方有紫色下划线指示器(Row width 20 height 3 primary 深紫圆角 2 margin 上 4),是本应用底部导航的特色设计,让用户一眼就能判断当前所在页面。
10.2 模拟数据与工具函数
private templates: PhotoTemplate[] = [ new PhotoTemplate('t1', '古风汉服写真', '国风', 99, '🏮', 3580, 4.9, '热门', true), ... ];
private photos: UserPhoto[] = [ new UserPhoto('p1', '紫禁之巅', '古风汉服写真', '08-19', '🏮', 328, true), ... ];
private orders: OrderInfo[] = [ new OrderInfo('o1', 'AI20260819001', '古风汉服写真', 99, '已完成', '08-19', 1), ... ];
private creativeTemplates: CreativeTemplate[] = [ new CreativeTemplate('c1', 'AI换脸', '上传照片一键换脸', '😊', '趣味', 8560), ... ];
private monthStats: MonthStat[] = [ { month: '3月', count: 28, avgScore: 4.6 }, ... ];
六组常量数据承载了幻影AI的全部业务信息:templates 10 个写真模板(覆盖国风/科幻/二次元/艺术/复古/梦幻/暗黑/婚纱/纪实十大风格,价格从 ¥89 到 ¥199,评分从 4.4 到 4.9,5 个 isHot 热门)、photos 12 个用户作品(5 个收藏,点赞数从 67 到 489)、orders 10 条订单(8 已完成/1 生成中/1 已退款,金额从 ¥89 到 ¥199)、creativeTemplates 8 个创意工具(覆盖趣味/修复/编辑/实用四大类别,下载量从 3450 到 9340)、monthStats 6 个月数据(生成数从 28 到 78,平均评分从 4.5 到 4.9)。所有 @Observed 类数据使用 new ClassName(...) 构造函数实例化,MonthStat 使用对象字面量。formatPrice 工具函数统一价格格式,是本应用唯一的工具函数。
十一、动态颜色与四动画状态:数据驱动的视觉映射与联动动效
应用通过动态颜色逻辑和四动画状态联动,将业务数据转化为视觉色彩与动态效果,实现"数据即配色、状态即动效"。
11.1 动态颜色逻辑
本应用的动态颜色逻辑分布在多个维度:
- 月度柱状图着色:
stat.count > 60 ? COLORS.danger : stat.count > 40 ? COLORS.primary : COLORS.accentLight,按生成数分三档(>60 红/>40 深紫/其余浅品红),5 月 62 和 8 月 78 用红色突出,符合假期和暑期高峰规律。 - 订单状态着色:
order.status === '已完成' ? COLORS.success : order.status === '生成中' ? COLORS.warning : COLORS.danger,按订单状态分三档(已完成绿/生成中橙/已退款红)。 - 风格分类着色:第一排国风/二次元/科幻/复古用 primary 深紫字,第二排艺术/梦幻/暗黑/婚纱用 accent 亮紫字,两排颜色区分形成视觉层次。
- 四栏统计着色:作品数 primary 深紫/订单数 accent 亮紫/积分 warning 橙/收藏 danger 红,四种颜色对应四种数据维度。
- 热门模板tag着色:热门模板的 tag 标签用 danger 红底白字,position 定位右上角,突出热门属性。
11.2 四动画状态联动
aboutToAppear(): void {
this.timerId = setInterval(() => {
this.breathAnim = !this.breathAnim;
this.blinkAnim = !this.blinkAnim;
this.pulseAnim = !this.pulseAnim;
this.floatAnim = !this.floatAnim;
}, 1500);
}
aboutToAppear 中启动 setInterval 1500ms 同步切换四个布尔动画状态(breathAnim/blinkAnim/pulseAnim/floatAnim),aboutToDisappear 中 clearInterval 清理定时器避免内存泄漏。四个动画状态分别用于不同元素:
- breathAnim:定义但未在 UI 中使用(预留扩展),体现了"预留动画状态"的工程习惯。
- blinkAnim:用于收藏作品列表的 ★星星缩放(
this.blinkAnim ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 },duration 500ms)和订单生成中状态缩放(order.status === '生成中' && this.blinkAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 },duration 500ms),两个元素共用同一 blinkAnim 状态,缩放比例不同(星星 1.1/状态 1.05),营造出"闪烁"的动态感。 - pulseAnim:用于热门模板 tag 缩放(scale 1.1,duration 600ms)、全部模板列表的 tag 缩放(scale 1.05,duration 600ms)、创意工具按钮缩放(scale 1.05,duration 700ms)、VIP 标签缩放(scale 1.05,duration 700ms),四个元素共用同一 pulseAnim 状态,缩放比例和 duration 不同,营造出"脉冲"的动态感。
- floatAnim:用于精选作品的收藏 ★星星缩放(
this.floatAnim && photo.isFavorite ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 },仅收藏的作品缩放,duration 500ms)和月度柱状图缩放(scale 1.03,duration 600ms),两个元素共用同一 floatAnim 状态,缩放比例不同,营造出"浮动"的动态感。
四个动画状态同步切换(1500ms 周期),分别用于不同元素,形成了整体协调的联动动效体系。blinkAnim 的收藏星星闪烁和订单生成中状态脉冲、pulseAnim 的热门标签脉冲和 VIP 标签缩放、floatAnim 的精选作品星星浮动和柱状图浮动,三种动效各司其职,让静态的页面有了"鲜活"的动态感,是本应用动效设计的核心亮点。特别的是,精选作品的收藏星星用 floatAnim(仅收藏的缩放),收藏作品列表的星星用 blinkAnim(全部缩放),两种不同的动效用于不同页面的收藏星星,体现了动效设计的细致考量。
十二、入口组件:应用的骨架与六Tab全差异化布局
12.1 组件声明与状态
@Entry
@Component
struct AIPhotoStudioApp {
@State currentTab: number = 0;
@State showGenerateModal: boolean = false;
@State showEditOrderModal: boolean = false;
@State showDeletePhotoModal: boolean = false;
@State selectedTemplate: PhotoTemplate = new PhotoTemplate('t0', '', '', 0, '', 0, 0, '', false);
@State selectedOrder: OrderInfo = new OrderInfo('o0', '', '', 0, '', '', 0);
@State selectedPhotoId: string = '';
@State credits: number = 580;
@State breathAnim: boolean = false;
@State blinkAnim: boolean = false;
@State pulseAnim: boolean = false;
@State floatAnim: boolean = false;
private timerId: number = -1;
}
14 个 @State 状态分六类:导航状态(currentTab)、弹窗控制状态(showGenerateModal/showEditOrderModal/showDeletePhotoModal)、弹窗数据状态(selectedTemplate/selectedOrder/selectedPhotoId,selectedTemplate 和 selectedOrder 用 new ClassName(...) 初始化空对象避免空引用)、业务数据状态(credits 积分余额 580)、动画状态(breathAnim/blinkAnim/pulseAnim/floatAnim)、定时器引用(private timerId)。private 字段(templates/photos/orders/creativeTemplates/monthStats/tabItems)存储静态模拟数据,不触发响应式更新。
12.2 生命周期与四动画联动
aboutToAppear(): void {
this.timerId = setInterval(() => {
this.breathAnim = !this.breathAnim;
this.blinkAnim = !this.blinkAnim;
this.pulseAnim = !this.pulseAnim;
this.floatAnim = !this.floatAnim;
}, 1500);
}
aboutToDisappear(): void {
if (this.timerId !== -1) {
clearInterval(this.timerId);
this.timerId = -1;
}
}
aboutToAppear 中启动 setInterval 1500ms 同步切换四个动画状态,实现四动画联动。aboutToDisappear 中判断 timerId 不为 -1 时 clearInterval 并重置为 -1,避免内存泄漏和重复启动。这种"生命周期管理定时器"的模式是 ArkUI 动效的标准实践。
12.3 紫色三色渐变头部(@Builder header)
顶部头部通过 @Builder header() 抽取,Row 横向布局,height 56,padding 左右 20,linearGradient 90 度从 primaryDark #4A148C 深紫黑 → primary #7B1FA2 深紫(0.5位置)→ accent #E040FB 亮紫品红的三色渐变,alignItems Center。内部 Column 左对齐 layoutWeight 1:"幻影AI"白色 22 号加粗 + "AI数字照相馆"白色 11 号 0.85 透明度 margin 上 2。右侧积分胶囊 Row:"🎨 " + this.credits(动态显示积分余额)白字 12 号,padding 左右 10 上下 6,半透明白底 rgba(255,255,255,0.2),圆角 16。紫色三色渐变头部是本应用最具辨识度的视觉元素,从深紫黑到深紫到亮紫品红的 90 度三色渐变,象征AI从神秘到创造的完整光谱,右侧积分胶囊动态显示积分余额,形成积分展示闭环。
12.4 底部 Tab 导航(带下划线指示器)与 Stack 弹窗
底部导航通过 @Builder tabBar() 构建,Row 内 ForEach 遍历 tabItems 6 个 Tab,每个 Tab Column 包裹:文字(11 号,激活 primary 深紫加粗,非激活 textSub 灰)+ 激活态下划线指示器(if currentTab === index 时显示 Row width 20 height 3 primary 深紫圆角 2 margin 上 4),height 52 layoutWeight 1 均分,点击切换 currentTab。底部 0.5px border 顶部分隔线。激活 Tab 的紫色下划线指示器是本应用底部导航的特色设计,比单纯文字变色更直观。
build() 使用 Stack 布局,主 Column 包含 header + if-else 切换 6 个 @Builder 页面(currentTab 0-5 对应 homeTab/photoTab/albumTab/orderTab/workshopTab/profileTab)+ tabBar,width/height 100%,bg 紫白底。弹窗条件渲染:if (this.showGenerateModal || this.showEditOrderModal || this.showDeletePhotoModal) 任一为 true 时渲染 this.modalOverlay(...),modalOverlay 内部 if-else 判断当前显示哪个弹窗内容,点击遮罩同时关闭所有三个弹窗状态。这种"统一 modalOverlay 封装 + 条件渲染"的模式大幅减少了重复代码,三个弹窗共享同一套遮罩与卡片样式。
十三、关键技术特性对比
| 特性维度 | 实现方式 | 技术要点 | 设计价值 |
|---|---|---|---|
| 色彩管理 | ColorPalette 接口 + COLORS 常量 | 集中定义 24 种语义色,含 chart1-6 六色紫系 | 保证视觉一致性,支持主题切换 |
| 渐变头部 | linearGradient 90度三色渐变 | primaryDark→primary→accent,白字标题+积分胶囊 | 最具辨识度的视觉元素,AI神秘创造光谱 |
| 数据建模 | @Observed 类 + constructor + interface | 4 个 @Observed 类(PhotoTemplate/UserPhoto/OrderInfo/CreativeTemplate)+ MonthStat interface | 按需选择响应式或静态,精准理解响应式机制 |
| 六Tab差异化 | 每页独立 @Builder 布局 | 首页Banner+横滑+分类+作品/写真三列网格+列表/相册四列网格+收藏/订单柱状图+列表/工坊双渐变卡+列表/我的用户卡+统计+权益+菜单 | 一页一型,还原真实AI写真App体验 |
| 底部导航 | 激活Tab下划线指示器 | width20 height3 primary色圆角2,比文字变色更直观 | 用户一眼判断当前页面 |
| 月度柱状图 | 三档着色 + 柱子内白字 + floatAnim | count>60红/>40紫/其余浅品红,height=count*1.5,scale1.03 | 以色定量级,假期暑期高峰红色突出 |
| 四动画联动 | setInterval 1500ms同步切换 | breathAnim/blinkAnim/pulseAnim/floatAnim四状态,分别用于不同元素 | 整体协调的联动动效体系 |
| 积分消耗闭环 | credits状态 + 生成弹窗消耗 | header显示积分胶囊,生成后credits-=price,我的页显示积分 | 积分获取-消耗-展示完整闭环 |
| 统一弹窗封装 | modalOverlay @Builder + if-else | 一个构建器封装三个弹窗内容,共享遮罩卡片样式,点击遮罩全关 | 大幅减少重复代码,样式统一 |
| 三大弹窗 | 生成AI写真/修改订单/删除作品 | 生成消耗积分/修改只读展示/删除二次确认 | 完整交互闭环 |
| 双渐变卡 | 工坊页两大卡片 | 左卡primary→accent亮渐变/右卡primaryDark→primary深渐变 | 深浅对比,主要入口更亮吸引点击 |
| 会员权益卡 | 90度紫色渐变 | primary→accent,与header 90度渐变呼应 | 统一紫色渐变视觉语言 |
| 工具函数 | formatPrice | ¥ + price.toFixed(2),统一价格格式 | 价格视觉语言一致 |
| 状态管理 | @State + @Observed + private | 14个@State分六类,private存静态数据不触发更新 | 细粒度状态控制,性能优化 |
| 组件化 | @Component + @Builder | 1个入口组件 + 6个页面@Builder + 3个弹窗内容@Builder + header/tabBar/modalOverlay | 高内聚低耦合,代码复用率高 |
| 列表渲染 | ForEach + 网格/横滑/列表 | 热门横滑/三列网格/四列网格/订单列表/创意工具列表/收藏列表 | 数据驱动渲染,布局灵活 |
| 数据可视化 | 柱状图 + 四栏统计 | 月度柱状图三档着色/我的页四栏统计四色 | 多维度呈现,直观易读 |
| 文本省略 | maxLines + textOverflow | 模板名maxLines1/作品标题maxLines1省略 | 长文本优雅截断,布局整齐 |
| 生命周期 | aboutToAppear/aboutToDisappear | setInterval启动/clearInterval清理,timerId判-1 | 健壮性强,无资源泄漏 |
| 布局系统 | Column/Row + Stack + Scroll + Circle | 弹性布局、Stack叠加、Scroll纵向滚动、Circle绘制头像/圆点 | 自适应布局,层次分明 |
完整代码
// 815.ets - 幻影AI·数字照相馆
// 紫色渐变主题 | 6tab单排 | 影视头部
interface ColorPalette {
bg: string;
cardBg: string;
primary: string;
primaryLight: string;
primaryDark: string;
accent: string;
accentLight: string;
text: string;
textSub: string;
textWhite: string;
border: string;
tagBg: string;
tagText: string;
shadow: string;
success: string;
warning: string;
danger: string;
chart1: string;
chart2: string;
chart3: string;
chart4: string;
chart5: string;
chart6: string;
}
const COLORS: ColorPalette = {
bg: '#F3E5F5',
cardBg: '#FFFFFF',
primary: '#7B1FA2',
primaryLight: '#CE93D8',
primaryDark: '#4A148C',
accent: '#E040FB',
accentLight: '#EA80FC',
text: '#1A1A2E',
textSub: '#9E9E9E',
textWhite: '#FFFFFF',
border: '#E1BEE7',
tagBg: '#F3E5F5',
tagText: '#7B1FA2',
shadow: '#D1C4E9',
success: '#4CAF50',
warning: '#FF9800',
danger: '#F44336',
chart1: '#7B1FA2',
chart2: '#E040FB',
chart3: '#9C27B0',
chart4: '#AB47BC',
chart5: '#CE93D8',
chart6: '#BA68C8',
};
@Observed
class PhotoTemplate {
id: string;
name: string;
style: string;
price: number;
image: string;
uses: number;
rating: number;
tag: string;
isHot: boolean;
constructor(id: string, name: string, style: string, price: number,
image: string, uses: number, rating: number, tag: string, isHot: boolean) {
this.id = id;
this.name = name;
this.style = style;
this.price = price;
this.image = image;
this.uses = uses;
this.rating = rating;
this.tag = tag;
this.isHot = isHot;
}
}
@Observed
class UserPhoto {
id: string;
title: string;
template: string;
date: string;
url: string;
likes: number;
isFavorite: boolean;
constructor(id: string, title: string, template: string, date: string,
url: string, likes: number, isFavorite: boolean) {
this.id = id;
this.title = title;
this.template = template;
this.date = date;
this.url = url;
this.likes = likes;
this.isFavorite = isFavorite;
}
}
@Observed
class OrderInfo {
id: string;
orderNo: string;
templateName: string;
amount: number;
status: string;
date: string;
count: number;
constructor(id: string, orderNo: string, templateName: string, amount: number,
status: string, date: string, count: number) {
this.id = id;
this.orderNo = orderNo;
this.templateName = templateName;
this.amount = amount;
this.status = status;
this.date = date;
this.count = count;
}
}
@Observed
class CreativeTemplate {
id: string;
name: string;
desc: string;
image: string;
category: string;
downloads: number;
constructor(id: string, name: string, desc: string, image: string,
category: string, downloads: number) {
this.id = id;
this.name = name;
this.desc = desc;
this.image = image;
this.category = category;
this.downloads = downloads;
}
}
interface MonthStat {
month: string;
count: number;
avgScore: number;
}
function formatPrice(price: number): string {
return '¥' + price.toFixed(2);
}
@Entry
@Component
struct AIPhotoStudioApp {
@State currentTab: number = 0;
@State showGenerateModal: boolean = false;
@State showEditOrderModal: boolean = false;
@State showDeletePhotoModal: boolean = false;
@State selectedTemplate: PhotoTemplate = new PhotoTemplate('t0', '', '', 0, '', 0, 0, '', false);
@State selectedOrder: OrderInfo = new OrderInfo('o0', '', '', 0, '', '', 0);
@State selectedPhotoId: string = '';
@State credits: number = 580;
@State breathAnim: boolean = false;
@State blinkAnim: boolean = false;
@State pulseAnim: boolean = false;
@State floatAnim: boolean = false;
private timerId: number = -1;
private templates: PhotoTemplate[] = [
new PhotoTemplate('t1', '古风汉服写真', '国风', 99, '🏮', 3580, 4.9, '热门', true),
new PhotoTemplate('t2', '赛博朋克霓虹', '科幻', 129, '🌃', 2890, 4.8, '新品', true),
new PhotoTemplate('t3', '日系动漫二次元', '二次元', 89, '🌸', 4520, 4.9, '热销', true),
new PhotoTemplate('t4', '油画肖像风格', '艺术', 159, '🎨', 1680, 4.7, '高端', false),
new PhotoTemplate('t5', '复古港风胶片', '复古', 109, '📸', 2340, 4.6, '经典', false),
new PhotoTemplate('t6', '水彩梦境少女', '梦幻', 119, '💧', 1950, 4.8, '推荐', true),
new PhotoTemplate('t7', '暗黑哥特风格', '暗黑', 139, '🦇', 890, 4.5, '限定', false),
new PhotoTemplate('t8', '工笔花鸟国风', '国风', 99, '🖌', 1230, 4.6, '新品', false),
new PhotoTemplate('t9', '婚纱写真大片', '婚纱', 199, '💒', 3120, 4.9, '热门', true),
new PhotoTemplate('t10', '黑白纪实摄影', '纪实', 89, '🖤', 670, 4.4, '经典', false),
];
private photos: UserPhoto[] = [
new UserPhoto('p1', '紫禁之巅', '古风汉服写真', '08-19', '🏮', 328, true),
new UserPhoto('p2', '霓虹都市', '赛博朋克霓虹', '08-18', '🌃', 256, false),
new UserPhoto('p3', '樱花飘雪', '日系动漫二次元', '08-17', '🌸', 489, true),
new UserPhoto('p4', '油画肖像', '油画肖像风格', '08-16', '🎨', 178, false),
new UserPhoto('p5', '港风回忆', '复古港风胶片', '08-15', '📸', 142, false),
new UserPhoto('p6', '梦境水彩', '水彩梦境少女', '08-14', '💧', 267, true),
new UserPhoto('p7', '暗夜玫瑰', '暗黑哥特风格', '08-13', '🦇', 89, false),
new UserPhoto('p8', '花鸟工笔', '工笔花鸟国风', '08-12', '🖌', 156, false),
new UserPhoto('p9', '白色誓言', '婚纱写真大片', '08-11', '💒', 432, true),
new UserPhoto('p10', '黑白纪实', '黑白纪实摄影', '08-10', '🖤', 67, false),
new UserPhoto('p11', '紫禁之巅2', '古风汉服写真', '08-09', '🏮', 298, false),
new UserPhoto('p12', '霓虹都市2', '赛博朋克霓虹', '08-08', '🌃', 312, true),
];
private orders: OrderInfo[] = [
new OrderInfo('o1', 'AI20260819001', '古风汉服写真', 99, '已完成', '08-19', 1),
new OrderInfo('o2', 'AI20260818002', '赛博朋克霓虹', 129, '已完成', '08-18', 1),
new OrderInfo('o3', 'AI20260817003', '日系动漫二次元', 89, '已完成', '08-17', 2),
new OrderInfo('o4', 'AI20260816004', '油画肖像风格', 159, '生成中', '08-16', 1),
new OrderInfo('o5', 'AI20260815005', '水彩梦境少女', 119, '已完成', '08-15', 1),
new OrderInfo('o6', 'AI20260814006', '婚纱写真大片', 199, '已完成', '08-14', 3),
new OrderInfo('o7', 'AI20260813007', '复古港风胶片', 109, '已退款', '08-13', 1),
new OrderInfo('o8', 'AI20260812008', '黑白纪实摄影', 89, '已完成', '08-12', 1),
new OrderInfo('o9', 'AI20260811009', '工笔花鸟国风', 99, '已完成', '08-11', 2),
new OrderInfo('o10', 'AI20260810010', '暗黑哥特风格', 139, '已完成', '08-10', 1),
];
private creativeTemplates: CreativeTemplate[] = [
new CreativeTemplate('c1', 'AI换脸', '上传照片一键换脸', '😊', '趣味', 8560),
new CreativeTemplate('c2', 'AI老照片修复', '修复模糊老照片', '📷', '修复', 6230),
new CreativeTemplate('c3', 'AI黑白上色', '黑白照片自动上色', '🌈', '修复', 4890),
new CreativeTemplate('c4', 'AI背景替换', '智能抠图换背景', '🖼', '编辑', 7120),
new CreativeTemplate('c5', 'AI漫画脸', '照片转漫画风', '📝', '趣味', 5670),
new CreativeTemplate('c6', 'AI证件照', '一键生成证件照', '🪪', '实用', 9340),
new CreativeTemplate('c7', 'AI婚纱预览', '试穿虚拟婚纱', '👰', '趣味', 3450),
new CreativeTemplate('c8', 'AI头像生成', '生成个性头像', '🎭', '趣味', 6780),
];
private monthStats: MonthStat[] = [
{ month: '3月', count: 28, avgScore: 4.6 },
{ month: '4月', count: 45, avgScore: 4.7 },
{ month: '5月', count: 62, avgScore: 4.8 },
{ month: '6月', count: 38, avgScore: 4.5 },
{ month: '7月', count: 55, avgScore: 4.7 },
{ month: '8月', count: 78, avgScore: 4.9 },
];
private tabItems: string[] = ['首页', '写真', '相册', '订单', '工坊', '我的'];
aboutToAppear(): void {
this.timerId = setInterval(() => {
this.breathAnim = !this.breathAnim;
this.blinkAnim = !this.blinkAnim;
this.pulseAnim = !this.pulseAnim;
this.floatAnim = !this.floatAnim;
}, 1500);
}
aboutToDisappear(): void {
if (this.timerId !== -1) {
clearInterval(this.timerId);
this.timerId = -1;
}
}
@Builder
header() {
Row() {
Column() {
Text('幻影AI')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textWhite)
Text('AI数字照相馆')
.fontSize(11)
.fontColor(COLORS.textWhite)
.opacity(0.85)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Row() {
Text('🎨 ' + this.credits)
.fontSize(12)
.fontColor(COLORS.textWhite)
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.backgroundColor('rgba(255,255,255,0.2)')
.borderRadius(16)
}
}
.width('100%')
.height(56)
.padding({ left: 20, right: 20 })
.linearGradient({
angle: 90,
colors: [[COLORS.primaryDark, 0.0], [COLORS.primary, 0.5], [COLORS.accent, 1.0]]
})
.alignItems(VerticalAlign.Center)
}
@Builder
tabBar() {
Row() {
ForEach(this.tabItems, (item: string, index: number) => {
Column() {
Text(item)
.fontSize(11)
.fontWeight(this.currentTab === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTab === index ? COLORS.primary : COLORS.textSub)
if (this.currentTab === index) {
Row()
.width(20)
.height(3)
.backgroundColor(COLORS.primary)
.borderRadius(2)
.margin({ top: 4 })
}
}
.layoutWeight(1)
.height(52)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentTab = index;
})
}, (item: string) => item)
}
.width('100%')
.height(52)
.backgroundColor(COLORS.cardBg)
.border({ width: 0.5, color: COLORS.border })
}
@Builder
homeTab() {
Scroll() {
Column() {
// 大Banner
Column() {
Text('🎬 AI写真新体验')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textWhite)
.margin({ top: 20 })
Text('一键生成你的专属大片')
.fontSize(13)
.fontColor(COLORS.textWhite)
.opacity(0.85)
.margin({ top: 6, bottom: 16 })
Row() {
Button()
.width(120)
.height(40)
.backgroundColor(COLORS.textWhite)
.borderRadius(20)
.onClick(() => {
this.showGenerateModal = true;
})
}
.margin({ bottom: 20 })
}
.width('100%')
.linearGradient({ angle: 135, colors: [[COLORS.primaryDark, 0.0], [COLORS.accent, 1.0]] })
.borderRadius(20)
.margin({ bottom: 16 })
.alignItems(HorizontalAlign.Center)
// 热门模板
Row() {
Text('🔥 热门模板')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('更多 >')
.fontSize(12)
.fontColor(COLORS.primary)
}
.width('100%')
.margin({ bottom: 12 })
Scroll() {
Row() {
ForEach(this.templates.filter((t: PhotoTemplate) => t.isHot), (tpl: PhotoTemplate) => {
Column() {
Text(tpl.image)
.fontSize(40)
.margin({ bottom: 6 })
Text(tpl.tag)
.fontSize(9)
.fontColor(COLORS.textWhite)
.backgroundColor(COLORS.danger)
.borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.position({ x: 70, y: 8 })
.scale(this.pulseAnim ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 })
.animation({ duration: 600, iterations: 1 })
Text(tpl.name)
.fontSize(12)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(tpl.style)
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
Row() {
Text('★' + tpl.rating.toFixed(1))
.fontSize(10)
.fontColor(COLORS.warning)
Text(' ' + tpl.uses + '人用')
.fontSize(9)
.fontColor(COLORS.textSub)
}
.margin({ top: 4 })
Text(formatPrice(tpl.price))
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
.margin({ top: 4 })
}
.width(100)
.padding(10)
.backgroundColor(COLORS.cardBg)
.borderRadius(14)
.margin({ right: 10 })
.onClick(() => {
this.selectedTemplate = tpl;
this.showGenerateModal = true;
})
}, (tpl: PhotoTemplate) => tpl.id)
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.margin({ bottom: 16 })
// 风格分类
Text('🎭 风格分类')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
Row() {
ForEach(['国风', '二次元', '科幻', '复古'], (style: string) => {
Column() {
Text(style)
.fontSize(13)
.fontColor(COLORS.primary)
.fontWeight(FontWeight.Bold)
}
.layoutWeight(1)
.height(40)
.justifyContent(FlexAlign.Center)
.backgroundColor(COLORS.tagBg)
.borderRadius(10)
.margin({ right: 8 })
}, (style: string) => style)
}
.width('100%')
.margin({ bottom: 8 })
Row() {
ForEach(['艺术', '梦幻', '暗黑', '婚纱'], (style: string) => {
Column() {
Text(style)
.fontSize(13)
.fontColor(COLORS.accent)
.fontWeight(FontWeight.Bold)
}
.layoutWeight(1)
.height(40)
.justifyContent(FlexAlign.Center)
.backgroundColor(COLORS.tagBg)
.borderRadius(10)
.margin({ right: 8 })
}, (style: string) => style)
}
.width('100%')
.margin({ bottom: 16 })
// 精选作品
Text('✨ 精选作品')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
Column() {
ForEach(this.photos.slice(0, 6), (photo: UserPhoto) => {
Row() {
Text(photo.url)
.fontSize(32)
.margin({ right: 12 })
Column() {
Text(photo.title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Text(photo.template)
.fontSize(11)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
Row() {
Text('❤ ' + photo.likes)
.fontSize(10)
.fontColor(COLORS.danger)
Text(' ' + photo.date)
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ left: 8 })
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text(photo.isFavorite ? '★' : '☆')
.fontSize(20)
.fontColor(photo.isFavorite ? COLORS.warning : COLORS.textSub)
.scale(this.floatAnim && photo.isFavorite ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 })
.animation({ duration: 500, iterations: 1 })
}
.width('100%')
.padding(12)
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (photo: UserPhoto) => photo.id)
}
.width('100%')
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
photoTab() {
Scroll() {
Column() {
Text('📸 AI写真模板')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
// 三列网格
Row() {
ForEach(this.templates.slice(0, 3), (tpl: PhotoTemplate) => {
Column() {
Text(tpl.image)
.fontSize(36)
.margin({ bottom: 4 })
Text(tpl.name)
.fontSize(11)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(formatPrice(tpl.price))
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
}
.layoutWeight(1)
.padding({ top: 14, bottom: 14 })
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ right: 8 })
.onClick(() => {
this.selectedTemplate = tpl;
this.showGenerateModal = true;
})
}, (tpl: PhotoTemplate) => tpl.id)
}
.width('100%')
.margin({ bottom: 8 })
Row() {
ForEach(this.templates.slice(3, 6), (tpl: PhotoTemplate) => {
Column() {
Text(tpl.image)
.fontSize(36)
.margin({ bottom: 4 })
Text(tpl.name)
.fontSize(11)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(formatPrice(tpl.price))
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
}
.layoutWeight(1)
.padding({ top: 14, bottom: 14 })
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ right: 8 })
.onClick(() => {
this.selectedTemplate = tpl;
this.showGenerateModal = true;
})
}, (tpl: PhotoTemplate) => tpl.id)
}
.width('100%')
.margin({ bottom: 8 })
Row() {
ForEach(this.templates.slice(6, 9), (tpl: PhotoTemplate) => {
Column() {
Text(tpl.image)
.fontSize(36)
.margin({ bottom: 4 })
Text(tpl.name)
.fontSize(11)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(formatPrice(tpl.price))
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
}
.layoutWeight(1)
.padding({ top: 14, bottom: 14 })
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ right: 8 })
.onClick(() => {
this.selectedTemplate = tpl;
this.showGenerateModal = true;
})
}, (tpl: PhotoTemplate) => tpl.id)
}
.width('100%')
.margin({ bottom: 16 })
// 详细模板列表
Text('📋 全部模板')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
ForEach(this.templates, (tpl: PhotoTemplate) => {
Row() {
Text(tpl.image)
.fontSize(32)
.margin({ right: 12 })
Column() {
Text(tpl.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Row() {
Text(tpl.style)
.fontSize(9)
.fontColor(COLORS.tagText)
.backgroundColor(COLORS.tagBg)
.borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(tpl.tag)
.fontSize(9)
.fontColor(COLORS.textWhite)
.backgroundColor(COLORS.primary)
.borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.margin({ left: 4 })
.scale(this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 })
.animation({ duration: 600, iterations: 1 })
}
.margin({ top: 4 })
Row() {
Text('★' + tpl.rating.toFixed(1))
.fontSize(10)
.fontColor(COLORS.warning)
Text(' ' + tpl.uses + '人使用')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ left: 6 })
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Column() {
Text(formatPrice(tpl.price))
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
Button()
.width(56)
.height(26)
.backgroundColor(COLORS.primary)
.borderRadius(13)
.margin({ top: 6 })
.onClick(() => {
this.selectedTemplate = tpl;
this.showGenerateModal = true;
})
}
.alignItems(HorizontalAlign.End)
}
.width('100%')
.padding(12)
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (tpl: PhotoTemplate) => tpl.id)
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
albumTab() {
Scroll() {
Column() {
Row() {
Text('🖼 我的相册')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.layoutWeight(1)
Text(this.photos.length + '张')
.fontSize(12)
.fontColor(COLORS.textSub)
}
.width('100%')
.margin({ bottom: 12 })
// 四列网格
Row() {
ForEach(this.photos.slice(0, 4), (photo: UserPhoto) => {
Column() {
Text(photo.url)
.fontSize(28)
.margin({ bottom: 4 })
Text(photo.title)
.fontSize(9)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text('❤' + photo.likes)
.fontSize(8)
.fontColor(COLORS.danger)
.margin({ top: 2 })
}
.layoutWeight(1)
.padding({ top: 12, bottom: 12 })
.backgroundColor(COLORS.cardBg)
.borderRadius(10)
.margin({ right: 6 })
}, (photo: UserPhoto) => photo.id)
}
.width('100%')
.margin({ bottom: 6 })
Row() {
ForEach(this.photos.slice(4, 8), (photo: UserPhoto) => {
Column() {
Text(photo.url)
.fontSize(28)
.margin({ bottom: 4 })
Text(photo.title)
.fontSize(9)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text('❤' + photo.likes)
.fontSize(8)
.fontColor(COLORS.danger)
.margin({ top: 2 })
}
.layoutWeight(1)
.padding({ top: 12, bottom: 12 })
.backgroundColor(COLORS.cardBg)
.borderRadius(10)
.margin({ right: 6 })
}, (photo: UserPhoto) => photo.id)
}
.width('100%')
.margin({ bottom: 6 })
Row() {
ForEach(this.photos.slice(8, 12), (photo: UserPhoto) => {
Column() {
Text(photo.url)
.fontSize(28)
.margin({ bottom: 4 })
Text(photo.title)
.fontSize(9)
.fontColor(COLORS.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text('❤' + photo.likes)
.fontSize(8)
.fontColor(COLORS.danger)
.margin({ top: 2 })
}
.layoutWeight(1)
.padding({ top: 12, bottom: 12 })
.backgroundColor(COLORS.cardBg)
.borderRadius(10)
.margin({ right: 6 })
.onClick(() => {
this.selectedPhotoId = photo.id;
this.showDeletePhotoModal = true;
})
}, (photo: UserPhoto) => photo.id)
}
.width('100%')
.margin({ bottom: 16 })
// 收藏列表
Text('⭐ 收藏作品')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
ForEach(this.photos.filter((p: UserPhoto) => p.isFavorite), (photo: UserPhoto) => {
Row() {
Text(photo.url)
.fontSize(32)
.margin({ right: 12 })
Column() {
Text(photo.title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Text(photo.template)
.fontSize(11)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
Row() {
Text('❤ ' + photo.likes)
.fontSize(10)
.fontColor(COLORS.danger)
Text(' ' + photo.date)
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ left: 8 })
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Text('★')
.fontSize(18)
.fontColor(COLORS.warning)
.scale(this.blinkAnim ? { x: 1.1, y: 1.1 } : { x: 1.0, y: 1.0 })
.animation({ duration: 500, iterations: 1 })
}
.width('100%')
.padding(12)
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (photo: UserPhoto) => photo.id)
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
orderTab() {
Scroll() {
Column() {
// 柱状图
Column() {
Text('月度生成统计')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 16 })
Row() {
ForEach(this.monthStats, (stat: MonthStat) => {
Column() {
Column() {
Text(stat.count.toString())
.fontSize(9)
.fontColor(COLORS.textWhite)
}
.width(26)
.height(stat.count * 1.5)
.backgroundColor(stat.count > 60 ? COLORS.danger : stat.count > 40 ? COLORS.primary : COLORS.accentLight)
.borderRadius(4)
.justifyContent(FlexAlign.End)
.padding({ top: 2 })
.scale(this.floatAnim ? { x: 1.03, y: 1.03 } : { x: 1.0, y: 1.0 })
.animation({ duration: 600, iterations: 1 })
Text(stat.month)
.fontSize(9)
.fontColor(COLORS.textSub)
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}, (stat: MonthStat) => stat.month)
}
.width('100%')
.height(160)
.alignItems(VerticalAlign.Bottom)
}
.width('100%')
.padding(16)
.backgroundColor(COLORS.cardBg)
.borderRadius(16)
.margin({ bottom: 16 })
// 订单列表
Row() {
Text('📋 订单记录')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('共' + this.orders.length + '单')
.fontSize(12)
.fontColor(COLORS.textSub)
}
.width('100%')
.margin({ bottom: 12 })
ForEach(this.orders, (order: OrderInfo) => {
Column() {
Row() {
Text(order.orderNo)
.fontSize(10)
.fontColor(COLORS.textSub)
.layoutWeight(1)
Text(order.status)
.fontSize(10)
.fontColor(order.status === '已完成' ? COLORS.success : order.status === '生成中' ? COLORS.warning : COLORS.danger)
.fontWeight(FontWeight.Bold)
.scale(order.status === '生成中' && this.blinkAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 })
.animation({ duration: 500, iterations: 1 })
}
.width('100%')
Row() {
Text('📸')
.fontSize(28)
.margin({ right: 12 })
Column() {
Text(order.templateName)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Text('数量 x' + order.count)
.fontSize(11)
.fontColor(COLORS.textSub)
.margin({ top: 4 })
Row() {
Text(order.date)
.fontSize(11)
.fontColor(COLORS.textSub)
.layoutWeight(1)
Text(formatPrice(order.amount))
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
}
.width('100%')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.margin({ top: 8 })
Row() {
if (order.status === '生成中') {
Text('⏳ AI正在生成中...')
.fontSize(11)
.fontColor(COLORS.warning)
.layoutWeight(1)
} else {
Text('')
.layoutWeight(1)
}
Button()
.width(60)
.height(24)
.backgroundColor(COLORS.cardBg)
.border({ width: 0.5, color: COLORS.border })
.borderRadius(12)
if (order.status === '已完成') {
Button()
.width(60)
.height(24)
.backgroundColor(COLORS.primary)
.borderRadius(12)
.margin({ left: 8 })
.onClick(() => {
this.selectedOrder = order;
this.showEditOrderModal = true;
})
}
}
.margin({ top: 8 })
}
.width('100%')
.padding(12)
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (order: OrderInfo) => order.id)
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
workshopTab() {
Scroll() {
Column() {
Text('🎨 创意工坊')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
// 创意工具大卡片
Row() {
Column() {
Text('🤖')
.fontSize(44)
.margin({ bottom: 8 })
Text('AI智能生成')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textWhite)
Text('上传照片·一键写真')
.fontSize(11)
.fontColor(COLORS.textWhite)
.opacity(0.85)
.margin({ top: 4 })
Button()
.width(100)
.height(32)
.backgroundColor(COLORS.textWhite)
.borderRadius(16)
.margin({ top: 12 })
.onClick(() => {
this.showGenerateModal = true;
})
}
.layoutWeight(1)
.padding({ top: 20, bottom: 20 })
.linearGradient({ angle: 135, colors: [[COLORS.primary, 0.0], [COLORS.accent, 1.0]] })
.borderRadius(16)
.alignItems(HorizontalAlign.Center)
Column() {
Text('📊')
.fontSize(44)
.margin({ bottom: 8 })
Text('批量处理')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textWhite)
Text('多图·一键搞定')
.fontSize(11)
.fontColor(COLORS.textWhite)
.opacity(0.85)
.margin({ top: 4 })
Button()
.width(100)
.height(32)
.backgroundColor(COLORS.textWhite)
.borderRadius(16)
.margin({ top: 12 })
}
.layoutWeight(1)
.padding({ top: 20, bottom: 20 })
.linearGradient({ angle: 135, colors: [[COLORS.primaryDark, 0.0], [COLORS.primary, 1.0]] })
.borderRadius(16)
.alignItems(HorizontalAlign.Center)
.margin({ left: 10 })
}
.width('100%')
.margin({ bottom: 16 })
// 创意模板列表
Text('🛠 创意工具')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 12 })
ForEach(this.creativeTemplates, (tpl: CreativeTemplate) => {
Row() {
Text(tpl.image)
.fontSize(32)
.margin({ right: 12 })
Column() {
Text(tpl.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Text(tpl.desc)
.fontSize(11)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
Row() {
Text(tpl.category)
.fontSize(9)
.fontColor(COLORS.tagText)
.backgroundColor(COLORS.tagBg)
.borderRadius(4)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
Text(' ' + tpl.downloads + '次下载')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ left: 6 })
}
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Button()
.width(28)
.height(28)
.backgroundColor(COLORS.primary)
.borderRadius(14)
.scale(this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 })
.animation({ duration: 700, iterations: 1 })
}
.width('100%')
.padding(12)
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (tpl: CreativeTemplate) => tpl.id)
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
profileTab() {
Scroll() {
Column() {
// 用户卡
Row() {
Circle({ width: 64, height: 64 })
.fill(COLORS.primaryLight)
Column() {
Text('幻影用户')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Row() {
Text('VIP')
.fontSize(10)
.fontColor(COLORS.textWhite)
.backgroundColor(COLORS.primary)
.borderRadius(8)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.scale(this.pulseAnim ? { x: 1.05, y: 1.05 } : { x: 1.0, y: 1.0 })
.animation({ duration: 700, iterations: 1 })
Text('积分 ' + this.credits)
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ left: 8 })
}
.margin({ top: 4 })
}
.layoutWeight(1)
.margin({ left: 12 })
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.backgroundColor(COLORS.cardBg)
.borderRadius(16)
.margin({ bottom: 16 })
// 统计四宫格
Row() {
Column() {
Text(this.photos.length.toString())
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
Text('作品数')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text(this.orders.length.toString())
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.accent)
Text('订单数')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('580')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.warning)
Text('积分')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column() {
Text('4')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.danger)
Text('收藏')
.fontSize(10)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 16, bottom: 16 })
.backgroundColor(COLORS.cardBg)
.borderRadius(16)
.margin({ bottom: 16 })
// 会员权益
Column() {
Text('👑 会员权益')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.textWhite)
Text('每日无限生成 · 优先队列 · 专属模板')
.fontSize(11)
.fontColor(COLORS.textWhite)
.opacity(0.85)
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.linearGradient({ angle: 90, colors: [[COLORS.primary, 0.0], [COLORS.accent, 1.0]] })
.borderRadius(16)
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
// 菜单列表
ForEach(['我的作品', '积分商城', '邀请好友', '帮助反馈', '关于我们'], (item: string) => {
Row() {
Text(item)
.fontSize(14)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('>')
.fontSize(14)
.fontColor(COLORS.textSub)
}
.width('100%')
.padding({ top: 14, bottom: 14, left: 16, right: 16 })
.backgroundColor(COLORS.cardBg)
.borderRadius(12)
.margin({ bottom: 8 })
}, (item: string) => item)
}
.padding({ left: 16, right: 16, top: 12, bottom: 16 })
.alignItems(HorizontalAlign.Start)
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.layoutWeight(1)
}
@Builder
modalOverlay(onClose: () => void) {
Column() {
Column() {
if (this.showGenerateModal) {
this.generateModalContent()
} else if (this.showEditOrderModal) {
this.editOrderModalContent()
} else if (this.showDeletePhotoModal) {
this.deletePhotoModalContent()
}
}
.width('90%')
.constraintSize({ maxHeight: '80%' })
.backgroundColor(COLORS.cardBg)
.borderRadius(20)
.padding(20)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.4)')
.onClick(() => {
onClose();
})
}
@Builder
generateModalContent() {
Column() {
Text('生成AI写真')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 16 })
Row() {
Text(this.selectedTemplate.image)
.fontSize(48)
.margin({ right: 12 })
Column() {
Text(this.selectedTemplate.name)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
Text(this.selectedTemplate.style)
.fontSize(12)
.fontColor(COLORS.textSub)
.margin({ top: 2 })
Text(formatPrice(this.selectedTemplate.price))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.primary)
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.margin({ bottom: 16 })
Row() {
Text('上传照片')
.fontSize(13)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('📷 选择')
.fontSize(13)
.fontColor(COLORS.primary)
}
.margin({ bottom: 12 })
Row() {
Text('生成数量')
.fontSize(13)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('4张')
.fontSize(13)
.fontColor(COLORS.primary)
.fontWeight(FontWeight.Bold)
}
.margin({ bottom: 12 })
Row() {
Text('消耗积分')
.fontSize(13)
.fontColor(COLORS.text)
.layoutWeight(1)
Text('-' + this.selectedTemplate.price + '积分')
.fontSize(13)
.fontColor(COLORS.danger)
.fontWeight(FontWeight.Bold)
}
.margin({ bottom: 20 })
Row() {
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.cardBg)
.border({ width: 0.5, color: COLORS.border })
.borderRadius(20)
.onClick(() => {
this.showGenerateModal = false;
})
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.primary)
.borderRadius(20)
.margin({ left: 10 })
.onClick(() => {
this.credits -= this.selectedTemplate.price;
this.showGenerateModal = false;
})
}
.width('100%')
}
.alignItems(HorizontalAlign.Start)
}
@Builder
editOrderModalContent() {
Column() {
Text('修改订单')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 16 })
Row() {
Text('订单号')
.fontSize(13)
.fontColor(COLORS.text)
.width(60)
Text(this.selectedOrder.orderNo)
.fontSize(13)
.fontColor(COLORS.textSub)
.layoutWeight(1)
}
.margin({ bottom: 12 })
Row() {
Text('模板')
.fontSize(13)
.fontColor(COLORS.text)
.width(60)
Text(this.selectedOrder.templateName)
.fontSize(13)
.fontColor(COLORS.textSub)
.layoutWeight(1)
}
.margin({ bottom: 12 })
Row() {
Text('金额')
.fontSize(13)
.fontColor(COLORS.text)
.width(60)
Text(formatPrice(this.selectedOrder.amount))
.fontSize(13)
.fontColor(COLORS.primary)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
}
.margin({ bottom: 12 })
Row() {
Text('状态')
.fontSize(13)
.fontColor(COLORS.text)
.width(60)
Text(this.selectedOrder.status)
.fontSize(13)
.fontColor(COLORS.success)
.layoutWeight(1)
}
.margin({ bottom: 20 })
Row() {
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.cardBg)
.border({ width: 0.5, color: COLORS.border })
.borderRadius(20)
.onClick(() => {
this.showEditOrderModal = false;
})
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.accent)
.borderRadius(20)
.margin({ left: 10 })
.onClick(() => {
this.showEditOrderModal = false;
})
}
.width('100%')
}
.alignItems(HorizontalAlign.Start)
}
@Builder
deletePhotoModalContent() {
Column() {
Text('删除作品')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(COLORS.text)
.margin({ bottom: 16 })
Text('确定删除作品 ' + this.selectedPhotoId + ' 吗?')
.fontSize(14)
.fontColor(COLORS.textSub)
.margin({ bottom: 8 })
Text('删除后无法恢复')
.fontSize(12)
.fontColor(COLORS.danger)
.margin({ bottom: 20 })
Row() {
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.cardBg)
.border({ width: 0.5, color: COLORS.border })
.borderRadius(20)
.onClick(() => {
this.showDeletePhotoModal = false;
})
Button()
.width('45%')
.height(40)
.backgroundColor(COLORS.danger)
.borderRadius(20)
.margin({ left: 10 })
.onClick(() => {
this.showDeletePhotoModal = false;
})
}
.width('100%')
}
.alignItems(HorizontalAlign.Start)
}
build() {
Stack() {
Column() {
this.header()
if (this.currentTab === 0) {
this.homeTab()
} else if (this.currentTab === 1) {
this.photoTab()
} else if (this.currentTab === 2) {
this.albumTab()
} else if (this.currentTab === 3) {
this.orderTab()
} else if (this.currentTab === 4) {
this.workshopTab()
} else {
this.profileTab()
}
this.tabBar()
}
.width('100%')
.height('100%')
.backgroundColor(COLORS.bg)
if (this.showGenerateModal || this.showEditOrderModal || this.showDeletePhotoModal) {
this.modalOverlay(() => {
this.showGenerateModal = false;
this.showEditOrderModal = false;
this.showDeletePhotoModal = false;
})
}
}
.width('100%')
.height('100%')
}
}
十四、开发环境搭建
14.1 安装 DevEco Studio
DevEco Studio 是 HarmonyOS 应用开发的官方 IDE。下载安装包后双击运行,进入安装欢迎界面,点击「下一步」继续。

14.2 选择安装路径
选择目标安装目录,建议安装到非系统盘,确认所需空间后点击「下一步」。

14.3 配置安装选项
设置环境变量,勾选创建桌面快捷方式、将 bin 文件夹添加到 PATH、添加上下文菜单,但是需要重启一下电脑使环境变量生效。

14.4 选择项目模板
新建一个空白模板,在模板市场中选择 Empty Ability。

14.5 配置项目信息
设置 API 为 24 的模板项目,项目名称 kfschool,Compatible SDK 6.1.1(24),设备类型勾选 Phone,点击完成后初始化项目,自动下载相关依赖。

十五、总结
通过对这款幻影AI数字照相馆应用的深度拆解,我们可以看到,一个优秀的移动端应用远不止是功能的堆砌,它更是架构设计、数据建模、视觉系统、交互逻辑、动效设计、防御性编程等多维度能力的综合体现。
在架构层面,该应用采用了"配置驱动 + 组件化 + 状态管理"的三层设计模式。配置层将所有可变的数据与视觉属性抽离为常量数组和元数据接口,使得业务逻辑与表现层彻底解耦;组件层将六个功能页面拆分为独立的 @Builder 构建器,每个页面采用完全不同的布局风格,实现"一页一型"的差异化设计;状态层利用 ArkUI 的 @State、@Observed 机制,实现了数据变化到 UI 刷新的自动响应,特别是四个动画状态的联动管理(setInterval 1500ms 同步切换 breathAnim/blinkAnim/pulseAnim/floatAnim)和弹窗数据状态的空对象初始化(new PhotoTemplate('t0',...) / new OrderInfo('o0',...) 避免空引用),体现了对状态管理的精准理解。
在数据层面,四个 @Observed 数据类(PhotoTemplate/UserPhoto/OrderInfo/CreativeTemplate)以 constructor 构造函数方式实例化,完整描述了幻影AI的全部业务信息。特别的是,MonthStat 采用普通 interface 接口而非 @Observed 类,因为月度数据是静态统计数据无需响应式更新,这种"按需选择 @Observed 或 interface"的设计体现了对响应式机制的精准理解,避免了不必要的响应式开销。formatPrice 工具函数统一价格格式为 ¥XX.XX,是本应用唯一的工具函数,体现了"最小工具集"的设计原则。
在视觉层面,本应用独创了"紫色渐变主题"设计——顶部 header 使用 linearGradient 90 度从 primaryDark #4A148C 深紫黑 → primary #7B1FA2 深紫(0.5位置)→ accent #E040FB 亮紫品红的三色斜向渐变,是本应用最具辨识度的视觉元素,从深紫黑到深紫到亮紫品红的 90 度三色渐变象征AI从神秘到创造的完整光谱。首页大Banner、工坊两大卡片、会员权益卡分别使用不同角度和配色的紫色渐变,形成了统一而丰富的紫色渐变视觉语言,贯穿整个应用。chart1-6 六种图表色全是紫色系,保证数据可视化与整体主题的一致性。应用的动态颜色逻辑也十分丰富——月度柱状图按生成数分三档着色(>60红/>40紫/其余浅品红)、订单状态按状态分三档着色(已完成绿/生成中橙/已退款红)、风格分类分两排着色(第一排深紫/第二排亮紫)、四栏统计分四色,形成了"以色定量级、以色定状态、以色定分类"的多层语义着色模式。统一的色彩系统是整个应用的视觉基石,深紫主色搭配亮紫品红辅助色,配合紫白底和多维度紫色渐变,营造出既神秘又浪漫的AI数字照相馆氛围。
在交互层面,应用独创了"统一 modalOverlay 封装"设计——一个 @Builder modalOverlay(onClose) 构建器,内部通过 if-else 判断当前显示哪个弹窗内容,外层统一全屏遮罩 + 居中卡片,点击遮罩同时关闭所有三个弹窗状态。这种统一封装大幅减少了重复代码,三个弹窗(生成AI写真/修改订单/删除作品)共享同一套遮罩与卡片样式。生成AI写真弹窗确认后 credits -= selectedTemplate.price(积分消耗闭环),header 积分胶囊实时更新积分余额,我的页显示积分,形成积分获取-消耗-展示的完整闭环。修改订单弹窗展示订单信息只读,删除作品弹窗二次确认后关闭,形成了完整的交互闭环。应用还独创了"四动画状态联动"设计——blinkAnim 用于收藏星星闪烁和订单生成中状态脉冲、pulseAnim 用于热门标签脉冲和 VIP 标签缩放、floatAnim 用于精选作品星星浮动和柱状图浮动,四个动画同步切换分别用于不同元素,形成了整体协调的联动动效体系,让静态的页面有了"鲜活"的动态感。月度柱状图按生成数分三档着色,柱子内白字显示数值,floatAnim 缩放动画让数据有了轻微的呼吸感;订单生成中状态的 blinkAnim 缩放 + "⏳ AI正在生成中…"提示让用户一眼就能判断订单流转状态;收藏星星的 blinkAnim 闪烁让收藏列表有了"闪烁"的动态感。激活 Tab 的紫色下划线指示器比单纯文字变色更直观,让用户一眼就能判断当前所在页面。

标签: HarmonyOS · API 24 · ArkTS · 华为 · AI写真 · 数字照相馆 · 移动端开发
更多推荐


所有评论(0)