【无标题】
基于HarmonyOS API 24与ArkTS声明式UI范式的球鞋定制工坊全栈实战:HarmonyOS 6.1.1单文件架构深度解析、Canvas数据可视化、动画交互与赛博风暗色主题设计全维度技术指南
一、技术背景与架构概览
1.1 HarmonyOS技术栈全景

HarmonyOS作为华为自研的分布式操作系统,历经多个版本的迭代演进,在HarmonyOS 6.1.1版本中迎来了声明式开发范式的全面成熟。基于HarmonyOS ArkTS API 24的开发框架,为开发者提供了从UI构建到状态管理、从动画系统到图形绘制的一站式解决方案。
ArkTS语言在TypeScript的基础上进行了深度扩展,引入了装饰器语法、自定义组件、状态管理等核心特性。这种声明式的UI开发范式,使得开发者可以通过描述"UI应该是什么样子"来构建界面,而无需关心底层的DOM操作和渲染细节。
在HarmonyOS 6.1.1中,ArkUI框架进一步优化了渲染性能,增强了组件化开发能力,并且提供了更加丰富的内置组件和API。基于HarmonyOS API 24的应用可以充分利用这些新特性,构建出流畅、美观、功能丰富的原生应用。
1.2 声明式UI范式的核心思想

声明式UI范式的核心在于"数据驱动视图"。当状态发生变化时,框架会自动重新计算并更新相关的UI部分,而开发者只需要关注状态的定义和状态与UI之间的映射关系。
这种范式与传统的命令式UI开发有着本质的区别。在命令式开发中,开发者需要手动获取DOM元素、修改属性、监听事件,每一步都需要明确的指令。而在声明式开发中,开发者只需要描述UI在不同状态下的样子,状态变化时的更新由框架自动完成。
ArkTS通过@State、@Prop、@Link等装饰器来管理组件状态,通过@Component装饰器来定义自定义组件,通过build()方法来描述UI结构。这种设计使得代码更加简洁、可读性更强,同时也更容易维护和测试。
1.3 单文件架构的设计理念

本文所分析的球鞋定制工坊应用采用了单文件架构的设计模式。整个应用的所有代码——包括枚举定义、接口声明、数据常量、辅助函数、入口组件以及六个功能Tab组件——全部集中在一个源文件中。
这种单文件架构在小型应用或原型开发阶段具有显著的优势。首先,它减少了文件跳转的开销,开发者可以在一个文件中浏览完整的应用逻辑。其次,它降低了模块间的耦合复杂度,所有的类型和数据都在同一作用域内可用。最后,它便于快速原型验证和代码分享。
当然,单文件架构也有其局限性。当应用规模扩大到一定程度时,单一文件会变得过于庞大,不利于团队协作和代码维护。但对于中小型应用或教学演示场景,单文件架构不失为一种高效的开发方式。
1.4 赛博风暗色主题的视觉设计

本应用采用了"炭黑 + 暗夜紫 + 电光绿"的赛博朋克风格配色方案。主背景色为深炭黑色#16161A,卡片背景为稍浅的深灰色#1F1F26,主色调为暗夜紫#7C4DFF,强调色为电光绿#B6FF00。
这种配色方案在暗色模式下具有强烈的视觉冲击力。紫色作为主色调传达出科技感和未来感,而电光绿作为强调色则提供了清晰的视觉焦点和交互反馈。文字颜色分为三个层级:主文字色#F2F2F7、次文字色#A8A8B3、辅助文字色#6E6E7A,确保了在暗色背景下的可读性。
此外,主题中还定义了危险色#FF4D5E和警告色#FFB300,用于状态标识和操作反馈。完整的配色系统通过一个统一的调色板对象进行管理,便于全局引用和主题切换。
1.5 整体架构层次图

二、数据层与类型系统深度解析

2.1 枚举类型的设计与应用
枚举类型是ArkTS中一种重要的类型定义方式,它允许我们为一组相关的常量赋予有意义的名称,从而提高代码的可读性和可维护性。
在本应用中,定义了一个名为SNTab的枚举类型,用于表示应用的六个功能Tab页。这个枚举包含了Home、Shoes、Color、Care、Custom、Mine六个成员,分别对应首页、球鞋、配色、护理、客制和我的六个页面。
使用枚举类型而非字符串字面量来表示Tab状态有诸多好处。首先,它提供了类型安全保障,编译器可以检查枚举值的合法性,避免了拼写错误导致的bug。其次,它使得代码更加自文档化,看到SNTab.Home就能立即明白其含义,而不需要猜测"home"或"Home"或"HOME"哪种写法是正确的。最后,枚举值支持IDE的自动补全,提升了开发效率。
2.2 接口定义与数据模型

接口是TypeScript和ArkTS中定义对象形状的核心机制。本应用共定义了十个接口,每个接口都精确描述了一类数据对象的结构和类型。
球鞋数据接口定义了球鞋的完整属性,包括唯一标识id、名称name、品类cat、现价price、原价oldPrice、热度值heat、主色调colorHex、是否限量limited以及标签tag。这些字段覆盖了商品展示所需的全部信息,从基本信息到营销属性,从视觉特征到状态标识。
配色项接口则相对简洁,包含id、名称、色值、系列和热度五个字段。这个接口主要用于配色选择和色系展示场景,重点在于颜色信息的表达。
护理产品接口的设计更加细致,除了基本的id、名称、价格外,还包含了功效描述、功效指数、使用占比、产品容量和适用材质等专业属性。这些属性支撑了护理产品的排行榜展示、用量分布统计和详情展示等功能。
定制方案接口描述了定制服务的核心属性,包括工艺类型、工期天数、价格、人气值和方案描述。其中工期和人气都通过进度条的形式进行可视化展示,使用户能够直观地了解每个方案的特点。
订单项接口记录了订单的关键信息,包括商品名称、订单状态、下单日期、价格和主色调。订单状态通过不同的颜色进行区分,已发货和已完成使用电光绿、定制中使用暗夜紫、待付款使用警告黄、其他状态使用辅助灰色。
收藏项接口则包含了商品名称、价格、备注和主色调四个核心字段。备注字段允许用户为每个收藏添加个性化的说明,增加了收藏功能的实用性。
消费记录接口用于月消费统计,只包含月份和金额两个字段。这个简单的数据结构支撑了柱状图的可视化展示。
功能项接口定义了首页功能导航宫格的数据结构,包含图标、名称和跳转目标Tab三个字段。这种设计使得功能导航可以完全由数据驱动,便于后续的功能扩展和配置化管理。
工艺统计接口用于定制工艺的单量统计,包含工艺名称和单量两个字段。这个数据驱动了工艺单量柱状图的渲染。
最后,调色板接口定义了完整的主题配色系统,包含背景色、卡片色、深卡片色、分割线色、主色调、深主色调、强调色以及三个层级的文字色,还有危险色和警告色。这种系统化的配色定义确保了整个应用视觉风格的一致性。
2.3 主题配置的系统化设计
主题配置是一个应用视觉系统的基石。本应用通过一个常量对象集中管理所有的颜色配置,这种设计模式被称为"设计令牌"(Design Tokens)。
设计令牌的核心理念是将设计决策(颜色、间距、字体等)抽象为可复用的变量,而不是在代码中硬编码具体的数值。这样做的好处是显而易见的:当需要调整配色方案时,只需要修改一处定义,整个应用的视觉风格就会统一更新。
在本应用中,调色板包含了十三种颜色变量,每种颜色都有明确的语义含义。背景色bg是整个应用的底色,卡片色card用于内容容器,深卡片色cardDeep用于卡片内的次级容器或需要视觉分层的区域,分割线色line用于边框和分隔符。
主色调primary是应用的品牌色,用于主要按钮、选中状态、重要强调等场景。深主色调primaryDeep则用于渐变的深色端或需要降低饱和度的场景。强调色accent是视觉焦点色,用于最重要的操作按钮、高亮文字、特殊标记等需要吸引用户注意力的地方。
文字颜色分为三个层级:主文字色textMain用于标题和重要内容,次文字色textSub用于正文和辅助信息,辅助文字色textDim用于次要说明和禁用状态。这种三级文字色体系确保了信息层级的清晰表达。
此外,还定义了危险色danger和警告色warn两种状态色,分别用于错误、删除等危险操作和提醒、待处理等警告状态。
2.4 数据常量的组织与管理
本应用定义了大量的数据常量,包括球鞋列表、配色列表、护理产品列表、定制方案列表、订单列表、收藏列表、消费记录、尺码数据、库存数据、首页功能项和工艺统计数据。
这些数据常量以数组的形式组织,每个数组元素都是一个符合对应接口定义的对象。数据常量的命名采用了全大写下划线分隔的命名约定,这是一种常见的常量命名规范,有助于在代码中快速识别常量值。
数据驱动是声明式UI的核心思想之一。所有的列表展示、图表渲染、功能导航都由这些数据常量驱动。当数据发生变化时,UI会自动更新。在实际的生产应用中,这些数据通常来自后端API接口,但在原型或演示阶段,使用本地数据常量可以快速搭建完整的UI界面。
球鞋数据数组包含了十款不同风格的球鞋,涵盖了跑步、篮球、休闲、滑板等多个品类,价格区间从799元到1899元不等,热度值从65到96分布。每款球鞋都有独特的主色调和标签,丰富了展示内容的多样性。
配色列表包含八种潮流配色,分布在紫系、荧光、暗色、粉系、蓝系、暖系、中性等多个色系中。每种配色都有对应的热度值,用于热度排行和饼图统计。
护理产品数组包含八款护理产品,从清洁到防护、从除臭到滋养,覆盖了球鞋护理的各个方面。功效指数从62到95不等,使用占比从8%到46%分布,这些数据为排行榜和用量分布提供了支撑。
定制方案数组包含六种定制工艺,从喷绘到刺绣、从激光到编织,每种方案有不同的工期、价格和人气值。这些数据支撑了客制方案列表和工艺统计图表。
订单列表包含六条订单记录,覆盖了已发货、定制中、已完成、待付款、已取消等多种订单状态。不同状态通过不同的颜色进行区分,提供了清晰的状态可视化。
收藏列表包含六条收藏记录,每条都有用户自定义的备注信息。收藏功能是电商应用中的常见功能,有助于用户管理感兴趣的商品。
消费记录数组包含六个月的消费数据,用于月消费统计柱状图。数据呈现波动上升的趋势,最高消费出现在6月份。
尺码数据和库存数据是两个平行数组,分别存储了欧洲码的尺码列表和对应尺码的库存数量。这种平行数组的设计在处理简单的关联数据时非常简洁高效。
首页功能项数组定义了八个功能入口,每个入口都有对应的图标、名称和跳转目标Tab。这种数据驱动的功能导航设计使得功能配置非常灵活。
工艺统计数组包含六种工艺的单量数据,用于工艺单量柱状图的展示。喷绘工艺的单量最高,达到156单。
2.5 辅助函数的实现与设计模式
辅助函数是数据层的重要组成部分,它们封装了常用的数据处理逻辑,使得组件代码更加简洁清晰。本应用共实现了十二个辅助函数,涵盖了数据过滤、排序、统计、格式化等多个方面。
球鞋过滤函数接收一个品类参数,返回该品类下的所有球鞋。当参数为"全部"时,返回所有球鞋。这个函数使用了简单的for循环和条件判断,虽然没有使用数组的filter方法,但逻辑清晰易懂。
限量球鞋筛选函数返回所有标记为限量的球鞋。这个函数的实现方式与过滤函数类似,都是通过遍历数组并根据条件收集符合条件的元素。
热销球鞋排序函数返回按热度降序排列的球鞋列表。值得注意的是,这个函数首先创建了一个原数组的浅拷贝,然后对拷贝数组进行排序,而不是直接修改原数组。这种不可变数据的处理方式是函数式编程的重要原则,它避免了副作用,使得数据变化更加可预测。
Top N球鞋函数在热销排序的基础上,截取前N个元素。这个函数复用了热销排序函数的结果,体现了代码复用的设计思想。
Top配色函数返回按热度降序排列的配色列表。实现方式与热销球鞋函数类似,都是先拷贝再排序。
Top护理产品函数返回按功效降序排列的护理产品列表。同样采用了先拷贝后排序的不可变数据处理方式。
总热度计算函数遍历配色列表,累加所有配色的热度值,返回总热度。这个值用于饼图的角度计算。
前序热度和函数计算指定索引之前(不包含当前索引)所有配色的热度之和。这个函数是饼图绘制的关键辅助函数,用于计算每个扇形的起始角度。
最大消费额函数遍历消费记录数组,找出最大的消费金额。这个值用于柱状图的高度比例计算。
最大工艺单量函数遍历工艺统计数组,找出最大的单量值。同样用于柱状图的比例计算。
最大库存函数遍历库存数组,找出最大库存量。用于尺码库存对比条的比例计算。
状态颜色函数根据订单状态返回对应的颜色值。已发货和已完成返回强调色,定制中返回主色调,待付款返回警告色,其他状态返回辅助文字色。这个函数封装了状态到颜色的映射逻辑,使得状态颜色的管理更加集中。
2.6 数据流向图
三、入口组件与导航架构分析
3.1 入口组件的结构设计
入口组件是整个应用的根组件,它承载了应用的整体布局和导航架构。在ArkTS中,入口组件通过@Entry装饰器标记,表示这是页面的入口点。
入口组件使用@Component装饰器定义,结构体名为SneakerLabApp。组件内部使用@State装饰器定义了一个状态变量activeTab,用于跟踪当前激活的Tab页,初始值为首页。
@State装饰器是ArkTS中最基础的状态管理装饰器。被@State标记的变量会被框架追踪,当变量值发生变化时,框架会自动重新渲染依赖该状态的UI部分。这种响应式的状态管理是声明式UI的核心机制。
入口组件的build()方法描述了应用的整体布局。整个应用采用垂直方向的三栏式布局:顶部是头部导航栏,中间是内容展示区,底部是Tab导航栏。这种布局结构是移动端应用的经典布局模式,符合用户的使用习惯。
3.2 顶部头部栏的实现
顶部头部栏采用水平方向的Row布局,包含三个部分:左侧的品牌标识区、中间的弹性空间、右侧的功能图标区。
品牌标识区使用Column垂直布局,上方是"SNEAK LAB"的品牌名称,使用了较大的字号、粗体和字间距,营造出潮流品牌的视觉效果。下方是副标题"球鞋定制工坊 · CUSTOM YOUR SOLE",使用了较小的字号和电光绿配色,与品牌名形成视觉层次。
功能图标区包含两个图标:通知图标和消息图标。通知图标使用Stack叠加布局,在铃铛图标上叠加了一个红色的小圆点,表示有未读通知。这种红点提示是移动应用中常见的通知提醒方式。消息图标则是一个简单的信封形状。
整个头部栏有底部边框,用于与内容区进行视觉分隔。背景色与应用背景色一致,保持了视觉的连贯性。
3.3 内容区与Tab切换逻辑
内容区是应用的核心展示区域,使用Stack堆叠布局。Stack布局的特点是子组件会堆叠在一起,后添加的组件会覆盖在先添加的组件之上。在本应用中,六个Tab页的内容都被放置在同一个Stack中,通过条件渲染来控制哪个Tab页显示。
条件渲染使用了if...else if...else的链式判断结构。根据activeTab状态变量的值,决定渲染哪个Tab组件。当activeTab为首页时,渲染首页Tab组件;为球鞋时,渲染球鞋Tab组件,以此类推。
这种条件渲染的Tab切换方式有其优缺点。优点是实现简单直观,每个Tab组件的生命周期与Tab的显示状态同步。缺点是每次切换Tab都会销毁和重建组件,导致组件状态丢失,而且对于复杂的Tab页,切换时可能会有性能开销。
首页Tab组件接收一个onGoTab回调函数参数,用于从首页跳转到其他Tab页。这种通过回调函数进行父子组件通信的方式,是ArkTS中组件间通信的常用模式。
3.4 底部Tab栏的设计与实现
底部Tab栏是应用的主导航入口,采用水平方向的Row布局,包含六个Tab项,分别对应首页、球鞋、配色、护理、客制和我的六个页面。
每个Tab项使用Column垂直布局,上方是图标,下方是文字。图标使用特殊的Unicode字符来表示,这是一种轻量级的图标方案,不需要额外的图标资源文件。文字使用较小的字号,确保Tab栏的紧凑性。
Tab项的选中状态通过颜色来区分。选中的Tab使用电光绿作为图标和文字的颜色,未选中的Tab使用辅助灰色。这种颜色对比清晰地表达了当前的激活状态。
每个Tab项都绑定了点击事件,点击时将activeTab状态设置为对应的枚举值。由于activeTab是@State状态变量,修改它会触发UI的重新渲染,从而切换到对应的Tab页。
Tab栏的高度为58像素,背景色为卡片色,顶部有一条分割线。这些设计细节与整体的视觉风格保持一致。
3.5 页面导航流程图
四、首页Tab深度解析
4.1 首页整体结构
首页是用户进入应用后看到的第一个页面,承担着内容展示、功能导航和氛围营造的重要作用。本应用的首页采用了纵向滚动的布局结构,从上到下依次排列了鞋型仪表盘、限量发售横滑、本周热销榜和功能导航宫格四个模块。
首页组件定义了一个状态变量snLaceY,用于控制鞋带摆动动画的位置。这个状态变量通过setInterval定时器周期性地更新,从而驱动鞋带的上下摆动效果。
aboutToAppear()和aboutToDisappear()是组件的生命周期钩子函数。aboutToAppear()在组件即将显示时调用,在这里启动了定时器。aboutToDisappear()在组件即将消失时调用,在这里清除定时器,避免内存泄漏。
这种生命周期管理对于包含定时器或订阅的组件来说至关重要。如果不在组件销毁时清除定时器,定时器会继续运行,导致内存泄漏和性能问题。ArkTS提供了完善的生命周期钩子函数,帮助开发者正确管理组件的生命周期。
4.2 鞋型仪表盘与鞋带动画
鞋型仪表盘是首页最上方的一个大卡片模块,用于展示平台的核心数据指标。这个模块分为上下两部分:上方是鞋带摆动动画区域,下方是三个数据指标。
鞋带摆动动画是本应用的一个视觉亮点。它通过模拟鞋带的上下摆动效果,营造出一种动态的、有生命力的视觉体验。这个动画完全使用声明式的方式实现,没有使用任何动画API,而是通过状态变量驱动UI变化来实现动画效果。
具体实现方式是:定义一个Y轴偏移量的状态变量,通过定时器周期性地增加这个偏移量,然后使用取模运算让偏移量在0到40之间循环。每个鞋带条使用translate变换属性,根据这个偏移量计算出对应的Y轴位移。
为了让鞋带摆动看起来更加自然,不同位置的鞋带条使用了不同的相位偏移。第一排的五个鞋带条分别有32、24、16、8、0的相位差,第二排的四个鞋带条分别有28、20、12、4的相位差。这种相位差使得鞋带条的摆动不是同步的,而是呈现出波浪式的递进效果,更加接近真实鞋带的摆动状态。
动画区域的背景使用了从深紫色到黑色的线性渐变,营造出科技感和深度感。左上角还有"LACE SYSTEM"的文字水印,增强了仪表盘的专业感。
数据指标区域展示了三个核心指标:今日热度指数96.2、定制单进行中128、本周新品6。每个指标使用垂直布局,上方是数值,下方是说明文字。指标之间使用竖线分隔,形成了清晰的视觉分区。
三个指标分别使用了不同的颜色:热度指数使用电光绿强调色,定制单使用暗夜紫主色调,新品数使用主文字色。这种颜色分配既符合各指标的语义,又形成了丰富的视觉层次。
4.3 限量发售横滑模块
限量发售模块展示了平台上的限量款球鞋,采用了横向滚动的展示方式。这种横滑布局在移动端非常常见,可以在有限的屏幕空间内展示更多的内容,同时也符合用户的滑动操作习惯。
横滑区域使用了Scroll组件配合水平方向的Row布局来实现。Scroll组件的scrollable属性设置为ScrollDirection.Horizontal,表示只允许水平方向滚动。scrollBar属性设置为BarState.Off,隐藏了滚动条,使界面更加简洁。
每个限量球鞋卡片使用垂直布局,从上到下依次是渐变色块、球鞋名称和价格。渐变色块使用了球鞋的主色调从左上角向右下角渐变到深卡片色,这种渐变效果使得色块更加有层次感和质感。
在色块的左上角叠加了一个"LIMITED"标签,使用电光绿背景和深色文字,突出了限量的稀缺属性。标签使用了圆角矩形的形状,与整体的圆角设计语言保持一致。
球鞋名称使用了单行省略的处理方式,当名称过长时会显示省略号,避免了布局错乱。价格使用了电光绿加粗的样式,突出了价格信息的重要性。
限量球鞋数据通过snLimitedShoes()辅助函数获取,这个函数筛选出所有标记为限量的球鞋。使用ForEach组件遍历数据数组,动态生成对应的卡片组件。ForEach的第二个参数是键生成函数,用于为每个列表项生成唯一的键,帮助框架高效地更新列表。
4.4 本周热销榜模块
本周热销榜展示了热度排名前五的球鞋,采用了排行榜的形式。排行榜是电商应用中常见的内容组织方式,可以激发用户的从众心理和购买欲望。
排行榜的每条记录包含排名序号、球鞋图片、球鞋信息和价格四个部分。排名序号使用了不同的颜色来区分前三名:第一名使用警告色(金色),第二名使用主文字色(银色),第三名及之后使用辅助文字色(铜色/普通)。这种颜色区分强化了排名的荣誉属性。
球鞋图片使用了渐变色块来模拟,同样采用了从球鞋主色调到深卡片色的线性渐变。球鞋信息区域包含球鞋名称、品类标签和热度值,以及一个热度进度条。
品类标签使用了紫色背景和紫色文字的胶囊形状,与主色调保持一致。热度值使用了辅助文字色,作为次要信息展示。热度进度条使用了从主色调到强调色的线性渐变,视觉效果非常炫酷。
进度条的实现使用了Stack堆叠布局:底层是灰色的背景条,上层是彩色的进度条。进度条的宽度通过热度值的百分比来控制,热度值越高,进度条越长。这种进度条的实现方式简单而有效,是UI开发中的常用技巧。
价格信息使用了电光绿加粗的样式,与整体的价格展示风格保持一致。
热销榜数据通过snTopShoes(5)辅助函数获取,这个函数先按热度排序,然后截取前五名。这种函数组合的方式体现了单一职责原则,每个函数只做一件事,然后通过组合来实现复杂的功能。
4.5 功能导航宫格模块
功能导航宫格是首页的底部模块,提供了八个功能入口的快捷访问。宫格布局使用Grid组件实现,设置为四列等宽布局。
Grid组件是ArkTS中用于网格布局的强大组件。通过columnsTemplate属性可以定义列的数量和宽度,columnsGap和rowsGap属性则分别定义列间距和行间距。与使用Row和Column嵌套相比,Grid组件在处理规则网格布局时更加简洁和高效。
每个功能入口使用垂直布局,上方是图标,下方是功能名称。图标使用特殊的Unicode字符表示,颜色为电光绿强调色。功能名称使用辅助文字色,字号较小。
功能入口点击后会调用onGoTab回调函数,跳转到对应的Tab页。这个回调函数是从父组件传入的,通过这种方式,子组件可以通知父组件进行状态变更,而不需要直接持有父组件的引用。
这种"数据驱动 + 回调通信"的设计模式是声明式UI开发中的最佳实践。子组件只负责展示数据和触发事件,真正的状态管理和业务逻辑由父组件或更高层级来处理。这种分层设计使得组件更加独立、可复用性更强。
4.6 首页渲染流程图
五、球鞋Tab深度解析
5.1 球鞋Tab整体架构
球鞋Tab是应用的商品展示核心页面,用户可以在这里浏览所有球鞋、按品类筛选、查看尺码库存和人气排行。球鞋Tab的整体布局采用了纵向滚动的结构,从上到下依次是分类筛选栏、球鞋双列Grid、尺码对比条和人气柱状图。
球鞋Tab定义了四个状态变量:shoeCat用于记录当前选中的品类,初始值为"全部";showEdit用于控制编辑参数弹框的显示状态;editShoe用于存储当前编辑的球鞋对象;editTarget和editHeat分别用于存储编辑弹框中的目标价格和热度阈值。
这种状态设计体现了"状态提升"的思想。编辑弹框的显示状态和编辑数据都由父组件管理,弹框本身只是一个纯展示的@Builder方法。这种设计使得状态管理更加集中,数据流更加清晰。
5.2 分类筛选栏的实现
分类筛选栏是球鞋Tab的顶部组件,提供了全部、跑步、篮球、休闲、滑板五个品类的筛选功能。筛选栏采用水平方向的Row布局,五个分类标签并排排列。
每个分类标签是一个Text组件,通过不同的背景色和文字色来区分选中和未选中状态。选中的标签使用电光绿背景和深色文字,未选中的标签使用卡片色背景和辅助文字色。这种胶囊形状的标签设计在移动端筛选场景中非常常见。
点击分类标签时,将shoeCat状态变量设置为对应的品类名称。由于shoeCat是@State状态变量,修改它会触发UI的重新渲染,从而更新标签的选中状态和下方的球鞋列表。
分类筛选的实现逻辑非常简洁:所有可选的分类值直接硬编码在模板中,而不是通过数据驱动。对于分类数量较少且相对固定的场景,这种硬编码的方式更加简单直接。如果分类数量较多或需要动态变化,则应该考虑使用数据驱动的方式。
5.3 双列球鞋Grid布局
球鞋展示区采用了双列Grid布局,这是电商应用中最常见的商品展示方式之一。双列布局在信息密度和可读性之间取得了良好的平衡,既能够展示足够多的商品,又不会因为过于拥挤而影响浏览体验。
Grid组件通过columnsTemplate('1fr 1fr')设置为两列等宽布局,columnsGap和rowsGap分别设置列间距和行间距。Grid的高度通过计算得出:球鞋数量除以2向上取整,再乘以每个卡片的高度(252像素)。这种动态高度计算确保了Grid容器能够完整容纳所有内容。
每个球鞋卡片使用垂直布局,从上到下依次是:渐变色块区域、球鞋名称、品类和热度信息、价格信息、编辑参数按钮。
渐变色块区域是卡片的视觉主体,使用了球鞋主色调到深色的线性渐变。在色块的右上角叠加了一个标签,显示球鞋的标签信息(如新品、热卖、经典等)。标签使用警告色背景和深色文字,起到了醒目的标记作用。
球鞋名称使用了单行省略的处理方式,确保名称不会因为过长而破坏布局。品类和热度信息使用了较小的字号,品类文字使用主色调,中间用一个点号分隔,热度使用辅助文字色。
价格区域包含现价和原价两部分。现价使用电光绿加粗的样式,是视觉焦点。原价使用辅助文字色并添加了删除线,用于对比展示折扣力度。
编辑参数按钮使用了深卡片色背景和辅助文字色,点击后会弹出编辑参数弹框。这个按钮的存在使得用户可以直接从商品列表进入参数编辑,提升了操作效率。
球鞋数据通过snFilterShoes(this.shoeCat)辅助函数获取,这个函数根据当前选中的品类返回对应的球鞋列表。当品类发生变化时,这个函数的返回值也会变化,从而触发Grid的重新渲染。
值得注意的是ForEach的键生成函数中包含了this.shoeCat。这是因为当品类切换时,Grid中的数据会完全变化,如果键只包含球鞋id,框架可能会错误地复用旧的组件实例。将品类信息加入键中,可以确保品类切换时组件会被正确地重建。
5.4 尺码对比条模块
尺码对比条模块展示了不同尺码的库存对比情况,帮助用户了解各尺码的供需关系。这个模块使用了横向进度条的形式,每个尺码对应一条进度条,进度条的长度表示库存的多少。
进度条的实现使用了Stack堆叠布局:底层是深灰色的背景条,上层是渐变色的进度条。进度条的宽度通过当前尺码库存除以最大库存再乘以100%来计算。渐变色从主色调到强调色,与整体的渐变设计语言保持一致。
每个尺码行包含三个部分:尺码数字、进度条、库存件数。尺码数字左对齐,库存件数右对齐,进度条占据中间的剩余空间。这种三栏式布局是列表展示的经典模式。
尺码数据和库存数据使用了两个平行数组SN_SIZES和SN_SIZE_STOCK,通过索引来对应。这种设计在数据量较小且关系简单时非常简洁,但在复杂场景下可能不如对象数组直观。
最大库存值通过snMaxSizeStock()辅助函数计算,这个函数遍历库存数组找出最大值。由于最大值是固定的,每次渲染时都会重新计算,这在数据量较小时不会有性能问题,但如果数据量很大,可以考虑使用缓存来优化。
5.5 人气柱状图模块
人气柱状图模块以柱状图的形式展示了全系列球鞋的热度排名。柱状图是数据可视化的基础图表类型,能够直观地展示数据之间的大小对比。
柱状图的实现完全使用基础组件构建,没有使用专门的图表库。每根柱子是一个Column组件,高度通过热度值乘以一个系数(1.05)来计算。前三名的柱子使用电光绿强调色,其余使用深主色调,通过颜色区分突出了前三名的特殊地位。
每根柱子的顶部显示具体的热度值,底部显示排名序号。柱子从底部向上生长,通过justifyContent(FlexAlign.End)实现底部对齐的效果。
整个柱状图区域使用Row布局,通过justifyContent(FlexAlign.SpaceBetween)让柱子均匀分布。Row的高度固定为160像素,确保所有柱子在同一基线上对齐。
人气数据通过snHotShoes()辅助函数获取,这个函数返回按热度降序排列的球鞋列表。柱状图的排列顺序与热度排序一致,最热门的球鞋排在最左边。
这种纯组件实现的柱状图虽然功能相对简单,但具有很好的灵活性和定制性。开发者可以完全控制柱子的样式、动画和交互,而不受图表库API的限制。
5.6 编辑参数弹框详解
编辑参数弹框是球鞋Tab中的一个交互组件,用户可以在这里调整目标入手价和热度提醒阈值。弹框使用@Builder装饰器定义为一个构建方法,通过条件渲染来控制显示和隐藏。
弹框采用了居中弹出的样式,背景是半透明的黑色遮罩层,中间是弹框内容。遮罩层使用#A6000000的颜色值,其中A6是alpha通道的十六进制值,约等于65%的不透明度。弹框内容使用卡片色背景,带有圆角和边框。
弹框的内容分为四个部分:标题区、价格滑块、热度滑块、操作按钮区。
标题区显示弹框标题"编辑定制参数"和当前编辑的球鞋名称,底部有一条分割线。这种标题区的设计是弹框组件的标准模式。
价格滑块区域包含标签行和滑块两部分。标签行左侧是"目标入手价"的说明文字,右侧是当前的价格数值,使用电光绿加粗突出显示。滑块使用Slider组件,最小值600,最大值2200,步长50,样式为OutSet(滑块在轨道外侧)。
滑块的选中轨道颜色为主色调,未选中轨道颜色为分割线色,滑块按钮颜色为强调色。这种配色方案使得滑块的当前位置清晰可见。滑块的值变化通过onChange事件监听,更新到对应的状态变量中。
热度滑块区域的结构与价格滑块类似,区别在于颜色配置:选中轨道颜色为强调色,滑块按钮颜色为主色调。这种颜色互换的设计形成了有趣的视觉对比。
操作按钮区包含"重置"和"保存参数"两个按钮。重置按钮使用深卡片色背景和辅助文字色,点击后将价格和热度恢复为默认值。保存按钮使用主色调背景和深色文字,点击后关闭弹框。
弹框的关闭只改变了showEdit状态,并没有真正保存数据。在实际应用中,这里应该调用保存接口或更新数据,但在原型阶段,简单的关闭就足够了。
5.7 状态管理状态图
六、配色Tab深度解析
6.1 配色Tab整体架构
配色Tab是应用的色彩探索页面,用户可以在这里浏览各种潮流配色、查看色系分布、选择配色方案。配色Tab的整体布局从上到下依次是喷漆调色台、潮流色卡Grid、色系分布饼图和配色热度排行。
配色Tab定义了四个状态变量:snSprayY用于控制喷漆粒子动画的位置;showScheme用于控制配色方案弹框的显示状态;selScheme用于记录弹框中选中的配色方案索引;mainColorIdx用于记录当前选中的主色调索引。
除了状态变量,配色Tab还定义了两个私有成员变量:sprayTimer用于存储喷漆动画的定时器ID;pieCtx是一个Canvas 2D渲染上下文对象,用于绘制饼图。
Canvas是HarmonyOS ArkTS API 24提供的强大图形绘制能力。通过Canvas API,开发者可以在画布上绘制各种图形、文字和图片,实现复杂的自定义绘制效果。在本应用中,Canvas被用于绘制色系分布饼图。
6.2 喷漆调色台动画
喷漆调色台是配色Tab的顶部视觉模块,模拟了喷漆下落的动画效果。这个动画模块营造了色彩创作的氛围,与配色主题高度契合。
喷漆动画的实现原理与首页的鞋带动画类似,都是通过定时器驱动状态变量变化,再由状态变量驱动UI变化。不同的是,喷漆动画模拟的是粒子下落的效果,而鞋带动画模拟的是摆动效果。
喷漆粒子使用Circle组件实现,共有八个不同颜色、不同大小的粒子。每个粒子有不同的水平位置和相位偏移,使得粒子下落看起来更加自然和随机。
粒子的Y轴位置通过snSprayY状态变量加上各自的相位偏移后取模计算得出。粒子的不透明度也随着Y轴位置变化,位置越低,不透明度越低,模拟了粒子逐渐消散的效果。
在喷漆粒子的下方,有一个当前色的展示区域,包含一个圆形的色样和当前色的名称、系列、色值信息。当前色由mainColorIdx状态变量控制,用户可以通过点击下方的色卡来切换当前色。
喷漆调色台的背景使用了从深紫色到黑色的线性渐变,与首页仪表盘的背景风格一致。左上角有"喷漆调色台"的标题和"喷涂中"的状态指示,右上角有一个向下的箭头图标,表示喷漆的方向。
6.3 潮流色卡Grid
潮流色卡Grid以三列网格的形式展示了所有可选的配色。每个色卡包含一个色块、配色名称和系列热度信息。
色卡的选中状态通过边框来区分。选中的色卡使用1.5像素的电光绿边框,未选中的使用1像素的分割线色边框。这种边框粗细和颜色的变化清晰地表达了选中状态。
点击色卡时,将mainColorIdx状态变量设置为对应的索引。由于mainColorIdx是@State状态变量,修改它会触发UI的重新渲染,从而更新色卡的选中边框和喷漆调色台的当前色显示。
Grid组件设置为三列等宽布局,高度固定为470像素。由于配色数量固定为8个,三列布局需要三行(前两行各3个,第三行2个),因此高度可以固定计算。
"方案选择"按钮位于模块标题的右侧,点击后弹出配色方案选择弹框。这个按钮使用电光绿文字,与普通的"查看更多"链接有所区别,暗示了这是一个更重要的操作入口。
6.4 色系分布饼图与Canvas绘制
色系分布饼图是配色Tab的核心数据可视化模块,展示了各种配色的热度占比。这个饼图完全使用Canvas API绘制,展示了ArkTS中自定义绘制的强大能力。
Canvas的使用分为两步:首先创建一个Canvas渲染上下文对象,然后在组件的onReady回调中调用绘制函数。onReady回调在Canvas组件准备就绪后触发,是开始绘制的最佳时机。
饼图的绘制逻辑封装在drawPie()方法中。绘制过程如下:
首先,遍历配色列表,为每个配色绘制一个扇形。扇形的绘制使用了Canvas的路径API:先调用beginPath()开始一条新路径,然后调用moveTo()移动到圆心位置,接着调用arc()绘制圆弧,最后调用closePath()闭合路径。
arc()方法接收六个参数:圆心x坐标、圆心y坐标、半径、起始角度、结束角度。起始角度和结束角度的计算是饼图绘制的关键。总角度为2π(即360度),每个扇形的角度占比等于该配色热度占总热度的比例。
为了让饼图从12点钟方向开始(而不是默认的3点钟方向),起始角度需要减去π/2(即90度)。前序热度和通过snHeatBefore()辅助函数计算,用于确定每个扇形的起始位置。
每个扇形使用对应配色的颜色进行填充。调用fillStyle设置填充颜色,然后调用fill()方法填充路径。
绘制完所有扇形后,再绘制一个内圆,将饼图变成环形。内圆使用卡片色填充,与背景融为一体,形成了镂空的效果。内圆的边框使用背景色,线宽为3像素,增加了环形的清晰度。
饼图的右侧是图例列表,每个图例包含一个颜色方块、配色名称和占比百分比。占比通过热度值除以总热度再乘以100%计算得出,保留一位小数。
图例与饼图配合使用,用户可以直观地了解每种配色的热度占比情况。这种"图表 + 图例"的组合是数据可视化的标准模式。
6.5 配色热度排行
配色热度排行以横向进度条的形式展示了所有配色的热度排名。这个模块的设计与球鞋Tab中的尺码对比条类似,但展示的内容和样式有所不同。
每条排行记录包含配色名称、热度进度条和热度数值三部分。配色名称左对齐,热度数值右对齐,进度条占据中间空间。
进度条的实现同样使用Stack堆叠布局,底层是深卡片色背景,上层是对应配色的颜色填充。进度条的宽度直接使用热度值作为百分比(因为热度值本身就是百分制的)。
热度数据通过snTopColors()辅助函数获取,这个函数返回按热度降序排列的配色列表。排行的顺序与热度排序一致,最热的配色排在最上面。
热度排行模块与色系分布饼图形成了互补:饼图展示了各配色的相对占比,而热度排行则展示了具体的热度数值和排名顺序。两种不同的可视化方式从不同角度呈现了同一组数据。
6.6 配色方案选择弹框
配色方案选择弹框提供了更加集中的配色选择界面。与直接点击色卡相比,弹框形式的选择可以提供更多的上下文信息和操作选项。
弹框的标题区包含标题文字和关闭按钮。标题下方有一行说明文字"点击色卡设为默认喷涂主色",引导用户进行操作。
配色选择区使用三列Grid布局,每个色卡包含一个色块和配色名称。选中的色卡有电光绿边框,并且色块上叠加了一个对勾标记。对勾的颜色根据背景色的深浅进行了调整:炭晶黑(id为3)的对勾使用电光绿,其他颜色的对勾使用深色,确保了在不同背景上的可读性。
弹框底部有"取消"和"应用方案"两个按钮。取消按钮使用深卡片色背景,点击后关闭弹框但不应用选择。应用方案按钮使用电光绿背景,点击后将选中的配色方案应用为主色调,然后关闭弹框。
应用方案的逻辑是将selScheme赋值给mainColorIdx。这里有一个细节值得注意:弹框中的选择(selScheme)和实际应用的主色调(mainColorIdx)是两个独立的状态变量。用户在弹框中选择配色后,只有点击"应用方案"才会真正生效,点击"取消"则放弃选择。这种"预选-确认"的交互模式给了用户更多的控制权,避免了误操作。
6.7 Canvas绘制流程图
七、护理Tab深度解析
7.1 护理Tab整体架构
护理Tab展示了球鞋护理相关的产品和信息,包括护理好物排行榜和护理用量分布两个主要模块。护理Tab的设计相对简洁,但在交互细节上有其独到之处。
护理Tab定义了两个状态变量:showCare用于控制护理详情弹框的显示状态;selCare用于存储当前选中的护理产品对象。
这两个状态变量的设计模式与球鞋Tab中的编辑弹框类似:一个布尔值控制弹框的显隐,一个对象存储弹框的数据。这种模式是弹框交互的标准设计,在整个应用中被多次使用。
7.2 护理好物榜
护理好物榜是护理Tab的核心模块,以排行榜的形式展示了护理产品的功效排名。排行榜前三名有特殊的颜色标识,突出了荣誉属性。
每条排行榜记录包含排名徽章、产品信息和功效进度条三个主要部分。
排名徽章使用圆形设计,不同名次有不同的颜色:第一名使用警告色(金色),第二名使用次文字色(银色),第三名使用铜色,第四名及以后使用深卡片色。徽章中的排名数字使用对比色,确保在不同背景上的可读性。
产品信息区域包含产品名称、价格、功效描述和功效进度条。产品名称和价格在同一行,名称左对齐,价格右对齐。价格使用电光绿加粗,与整体的价格展示风格一致。
功效描述使用较小的字号和辅助文字色,作为产品的简要说明。功效进度条使用Stack布局实现,底层是灰色背景,上层是彩色进度。前三名的进度条使用电光绿强调色,其余使用主色调。这种颜色区分与排名徽章的颜色体系相呼应。
进度条下方有一行辅助信息,左侧显示功效数值,右侧显示"详情"链接。点击整条记录可以查看产品详情,"详情"链接起到了视觉提示的作用。
护理产品数据通过snTopCares()辅助函数获取,这个函数返回按功效降序排列的护理产品列表。排行榜的顺序与功效排序一致。
7.3 护理用量分布
护理用量分布模块展示了各护理产品的使用占比情况。这个模块采用了横向进度条的形式,与配色Tab中的热度排行和球鞋Tab中的尺码对比条类似,但样式有所不同。
每条用量记录包含产品名称、用量进度条和用量百分比三部分。产品名称固定宽度为76像素,确保左对齐整齐。用量百分比右对齐,使用主文字色。进度条占据中间的剩余空间。
进度条使用了从主色调到浅紫色的线性渐变,与其他模块的单色进度条形成了区分。渐变进度条在视觉上更加丰富,也与护理产品的"渐变功效"概念相呼应。
用量百分比的计算方式是使用量值乘以2。这里隐含了一个假设:使用量的最大值是50%,乘以2后得到100%的进度条宽度。这种设计使得用量数据在进度条上有更好的展示效果,避免了因为数值偏小而导致进度条过短的问题。
护理用量分布数据直接使用SN_CARES数组,没有进行排序。展示顺序与数据定义的顺序一致,即按产品ID排序。这与好物榜的按功效排序形成了对比,两个模块从不同维度展示了护理产品的信息。
7.4 护理详情弹框
护理详情弹框展示了护理产品的详细参数信息,采用了参数表的形式。与球鞋Tab的编辑弹框和配色Tab的方案弹框不同,护理详情弹框主要用于信息展示,交互操作较少。
弹框的顶部是标题区,包含一个紫色的产品图标、产品详情标题和产品名称,以及关闭按钮。产品名称使用电光绿,突出了产品标识。标题区底部有一条分割线。
参数表部分采用了多行列布局,每行包含参数名称和参数值两列。参数名称左对齐,使用辅助文字色;参数值右对齐,使用主文字色。每行底部有一条分割线,形成了清晰的表格结构。
参数表包含以下参数:产品名称、主要功效、功效指数、使用占比、产品容量、适用材质、参考价格。这些参数覆盖了护理产品的核心信息,用户可以通过这些参数全面了解产品的特点。
其中,功效指数和参考价格使用了特殊的样式突出显示。功效指数使用电光绿加粗,参考价格使用较大字号的电光绿加粗。这种视觉强调反映了这两个参数在用户购买决策中的重要性。
弹框的底部是一个"加入护理清单"按钮,使用电光绿背景和深色文字,占据全宽。点击按钮后关闭弹框,模拟了加入清单的操作。
护理详情弹框的设计体现了"信息展示为主,操作为辅"的原则。大部分空间用于展示产品信息,只有一个主要操作按钮。这种设计适用于详情查看类的弹框场景。
7.5 弹窗交互流程图
八、客制Tab深度解析
8.1 客制Tab整体架构
客制Tab是应用的定制服务页面,用户可以在这里了解定制方案、发起定制订单、查看工艺统计。客制Tab的整体布局从上到下依次是发起定制入口、热门客制方案列表和工艺单量统计柱状图。
客制Tab定义了四个状态变量:showAdd用于控制新增定制弹框的显示状态;newName用于存储输入的鞋款名称;selSize用于存储选中的尺码,初始值为41;selColorIdx用于存储选中的配色索引,初始值为0。
这四个状态变量分别对应弹框中的三个输入项和弹框的显隐控制。这种设计模式与其他Tab中的弹框设计保持一致,体现了整个应用交互模式的统一性。
8.2 发起定制入口卡
发起定制入口卡是客制Tab最上方的一个大卡片,是发起定制订单的主要入口。这个卡片采用了渐变背景和大号加号按钮的设计,视觉上非常醒目。
卡片的左侧是文字说明区域,包含主标题"发起定制"和副标题"专属鞋款 · 专属配色 · 专属工艺"。主标题使用较大的字号和粗体,副标题使用较小的字号和浅紫色。两行文字形成了清晰的视觉层次。
卡片的右侧是一个圆形的加号按钮,使用电光绿背景和深色加号图标。按钮的尺寸较大(52x52像素),具有很强的点击引导性。
整个卡片的背景使用了从深主色调到更深紫色的线性渐变,营造出高端定制的氛围。卡片的圆角为16像素,与其他卡片的圆角设计保持一致。
点击卡片的任意位置都会触发弹框显示,而不仅仅是点击加号按钮。这种大点击区域的设计提升了用户操作的便利性,符合移动端的交互设计原则。
8.3 热门客制方案列表
热门客制方案列表展示了六种定制工艺方案,采用了横向卡片的形式。每张卡片左侧是工艺图标区,右侧是方案详情区。
工艺图标区使用垂直布局,上方是工艺图标(使用Unicode字符表示),下方是工艺名称。图标区的背景使用了从深主色调到深卡片色的线性渐变,图标颜色为电光绿,工艺名称颜色为浅紫色。整个图标区有明确的视觉风格,突出了工艺属性。
方案详情区包含多个信息层级:
最上方是方案名称和价格,在同一行显示。名称左对齐,使用粗体主文字色;价格右对齐,使用电光绿粗体。这是电商卡片的标准布局方式。
名称下方是方案描述,使用较小的字号和辅助文字色。描述文字限制为单行,超出部分省略,确保卡片布局的整洁。
再往下是工期信息区域,包含工期文字和工期进度条。进度条显示当前工期占最长工期(30天)的比例。进度条的颜色根据工期长短而变化:工期超过10天使用警告色,10天以内使用主色调。这种颜色变化直观地反映了工期的长短,帮助用户快速了解方案的时间成本。
最下方是人气信息,包含人气文字和人气进度条。人气进度条使用电光绿,宽度固定为90像素。人气值以百分比的形式展示在进度条中。
方案卡片的底部有一条分割线,分隔了方案描述和详细参数。这种分区设计使得信息层次更加清晰。
定制方案数据直接使用SN_CUSTOMS数组,展示顺序与数据定义的顺序一致。
8.4 工艺单量统计柱状图
工艺单量统计柱状图展示了各种工艺的本月单量情况。这个柱状图的设计与球鞋Tab中的人气柱状图类似,但样式和数据有所不同。
每根柱子的顶部显示单量数值,底部显示工艺名称。柱子从底部向上生长,高度根据单量占最大单量的比例计算得出。
与球鞋Tab中纯色的柱子不同,工艺柱状图的柱子使用了从上到下的线性渐变,从电光绿渐变到主色调。这种渐变效果使得柱子更加有立体感和质感。
柱状图区域使用Row布局,通过justifyContent(FlexAlign.SpaceBetween)让柱子均匀分布。整个区域有14像素的内边距,确保柱子与边缘有足够的间距。
工艺统计数据使用SN_CRAFT_STATS数组,最大单量通过snMaxCraft()辅助函数计算。柱状图的总高度为150像素,柱子的最大高度为96像素(通过比例计算得出),剩余的空间用于显示顶部的数值和底部的名称。
这种纯组件实现的柱状图虽然简单,但对于展示少量分类的对比数据已经足够。如果需要更复杂的图表功能(如交互、动画、多系列等),可以考虑使用专门的图表库或基于Canvas进行更复杂的绘制。
8.5 新增定制弹框
新增定制弹框是客制Tab中的核心交互组件,用户可以在这里输入鞋款名称、选择尺码和配色,然后提交定制订单。
弹框包含三个输入区域:鞋款名称输入、尺码选择、配色选择。底部是提交按钮。
鞋款名称输入使用TextInput组件,提供了占位符文字提示。输入框使用深卡片色背景,有10像素的圆角和左右内边距。输入的文字实时更新到newName状态变量中。
尺码选择区域提供了38到44共七个尺码选项,使用横向排列的胶囊按钮形式。选中的尺码使用电光绿背景和深色文字,未选中的使用深卡片色背景和辅助文字色。点击尺码按钮更新selSize状态变量。
配色选择区域提供了八个配色选项,使用圆形色样的形式。选中的配色有2.5像素的电光绿边框,未选中的有1像素的分割线色边框。点击色样更新selColorIdx状态变量。
选中配色的下方有一行文字提示,显示当前选中的配色名称和系列。这行文字为用户提供了明确的选择反馈。
弹框的底部是"提交定制单"按钮,使用电光绿背景和深色文字,占据全宽。点击按钮后关闭弹框,模拟了提交定制单的操作。
新增定制弹框的设计体现了表单交互的最佳实践:每个输入项都有明确的标签说明,选中状态有清晰的视觉反馈,操作按钮醒目易点击。
8.6 组件依赖图
九、我的Tab深度解析
9.1 我的Tab整体架构
我的Tab是应用的个人中心页面,展示了用户的会员信息、消费统计、订单列表和收藏列表。我的Tab是六个Tab中功能最丰富的一个,包含了多个数据展示模块和交互功能。
我的Tab定义了五个状态变量:snShine用于控制会员卡光泽动画的位置;showDel用于控制删除确认弹框的显示状态;delIdx用于记录待删除的收藏项索引;favList用于存储收藏列表数据,初始值从SN_FAVORITES常量获取。
除了状态变量,还有一个私有成员变量shineTimer用于存储光泽动画的定时器ID。
收藏列表使用@State装饰器标记,这意味着当数组内容发生变化时(如添加或删除元素),UI会自动更新。这是通过框架对数组的变异方法(如splice、push、pop等)进行拦截来实现的。
9.2 会员卡与光泽扫过动画
会员卡是我的Tab最上方的视觉焦点,展示了用户的会员等级、积分、优惠券和好评率等核心信息。会员卡最引人注目的是其光泽扫过动画效果。
光泽动画的实现原理是:在会员卡的渐变背景之上,叠加一个半透明的渐变光条,通过定时器不断改变光条的水平位置,营造出光泽在卡片上扫过的效果。
光条使用一个Column组件实现,背景是从完全透明到半透明白色再到完全透明的线性渐变。光条被旋转了16度,使得光泽看起来是斜着扫过的。光条的水平位置由snShine状态变量控制,通过translate变换属性实现位移。
定时器每40毫秒将snShine的值增加6,当光条移动到卡片右侧之外时,重置到左侧重新开始。这个循环过程创造了持续的光泽扫过效果。
会员卡的内容分为三个层级:最上方是会员标识区,中间是会员编号和等级,最下方是数据指标区。
会员标识区左侧是"SNEAK VIP"的会员品牌名,使用电光绿和字间距,营造出高端感。右侧是一个大号的星形图标,使用深紫色,作为装饰元素。
会员编号区左侧是会员编号"NO. 20260826",使用浅紫色和字间距,模仿了会员卡的钢印效果。右侧是会员等级"S7 黑金会员",使用辅助文字色。
数据指标区展示了三个核心指标:积分12860、优惠券12张、好评率96%。三个指标之间使用竖线分隔,形成清晰的分区。最右侧是一个"充值"按钮,使用电光绿背景,引导用户进行充值操作。
整个会员卡使用从主色调到深紫色再到黑色的线性渐变背景,边框为深主色调,圆角为18像素。卡片的视觉效果非常接近真实的会员卡,光泽动画则增添了动感和高级感。
9.3 月消费统计柱状图
月消费统计柱状图展示了用户近六个月的消费趋势。这个柱状图的设计与其他Tab中的柱状图类似,但展示的数据和细节有所不同。
柱状图有六根柱子,分别对应3月到8月的消费数据。每根柱子的顶部显示消费金额(以k为单位,保留一位小数),底部显示月份。柱子的高度根据消费金额占最大消费额的比例计算得出。
最高消费额的柱子使用电光绿强调色,其余柱子使用深主色调。这种颜色区分突出了消费最高的月份,帮助用户快速识别消费峰值。
消费数据使用SN_SPENDS数组,最大消费额通过snMaxSpend()辅助函数计算。柱状图的总高度为140像素,柱子的最大高度为88像素。
月消费统计是个人中心常见的数据可视化模块,帮助用户了解自己的消费习惯和趋势。通过简单的柱状图,用户可以直观地看到消费的波动情况。
9.4 我的订单列表
我的订单列表展示了用户的历史订单记录。每条订单记录包含商品缩略图、商品信息和箭头图标三部分。
商品缩略图使用渐变色块实现,从订单商品的主色调渐变到深卡片色。缩略图的尺寸为46x46像素,圆角为10像素。
商品信息区域包含两行内容:上方是商品名称和订单状态,下方是下单日期和价格。商品名称左对齐,订单状态右对齐。订单状态的颜色通过snStatusColor()辅助函数根据状态文本动态计算:已发货和已完成为电光绿,定制中为主色调,待付款为警告色,其他状态为辅助灰色。
日期和价格信息使用较小的字号和辅助文字色,作为次要信息展示。价格信息直接拼接在日期后面,用一个点号分隔。
每条记录的右侧有一个箭头图标,表示可以点击进入详情。这个箭头图标是列表项的常见视觉提示,暗示了"更多"或"进入"的含义。
订单数据使用SN_ORDERS数组,展示顺序与数据定义的顺序一致,即按下单时间倒序排列(最新的订单在最上面)。
9.5 我的收藏与删除功能
我的收藏列表展示了用户收藏的商品。与订单列表类似,每条收藏记录也包含商品色块、商品信息和操作按钮三部分。
商品色块直接使用收藏商品的主色调填充,而不是渐变色。这种纯色块的设计与订单列表的渐变色块形成了区分,也使得收藏列表看起来更加简洁。
商品信息区域包含商品名称和备注价格信息。名称使用主文字色,备注和价格使用辅助文字色。备注字段是收藏功能的特色,允许用户为每个收藏添加个性化的说明。
操作按钮是一个红色的叉号按钮,使用危险色背景(半透明)和危险色图标。点击这个按钮会弹出删除确认弹框,确认后删除对应的收藏项。
收藏列表的数据存储在favList状态变量中,这是一个@State装饰的数组。当调用splice()方法删除元素时,框架会检测到数组的变化,并自动更新UI。
删除操作的实现分为三步:首先点击删除按钮设置待删除的索引并显示确认弹框;然后在弹框中点击确认后调用removeFav()方法;最后removeFav()方法使用splice()删除数组元素,关闭弹框,重置索引。
这种"点击-确认-执行"的三步删除流程是删除操作的标准交互模式,通过确认弹框防止用户误删。
9.6 删除确认弹框
删除确认弹框是一个警告性质的弹框,用于在执行删除操作前向用户确认。这个弹框采用了红色警告主题,与其他弹框的紫色/绿色主题形成了明显的区分。
弹框的顶部是一个大号的警告图标(⚠),使用危险色,立即传达了警告的含义。警告图标下方是确认标题"确认删除该收藏?",使用粗体主文字色。
标题下方是一段说明文字,解释了删除的后果:"删除后将从收藏列表中移除,该操作不可恢复。"这段文字使用辅助文字色,居中对齐。
说明文字下方有一个提示条,显示即将删除的收藏项名称。提示条使用半透明的危险色背景和危险色文字,再次强调了删除的对象。
弹框的底部有两个按钮:“取消"和"确认删除”。取消按钮使用深卡片色背景和辅助文字色,确认删除按钮使用危险色背景和白色文字。两个按钮的颜色对比强烈,清晰地区分了"安全"和"危险"操作。
删除确认弹框的边框也使用了半透明的危险色,进一步强化了警告的视觉效果。
删除确认弹框的设计充分体现了"危险操作二次确认"的交互设计原则。通过醒目的警告图标、明确的说明文字和颜色区分的操作按钮,最大程度地降低了用户误操作的风险。
9.7 动画系统流程图
十、核心技术模式深度总结
10.1 声明式UI的构建模式
整个应用充分体现了ArkTS声明式UI的构建模式。每个组件通过@Component装饰器定义,在build()方法中描述UI的结构和样式。状态变量通过@State装饰器标记,状态变化自动驱动UI更新。
这种声明式的开发方式与传统的命令式开发有着本质的区别。在命令式开发中,开发者需要手动获取DOM元素、设置属性、添加事件监听。而在声明式开发中,开发者只需要描述"UI在什么状态下应该是什么样子",框架负责处理状态变化到UI更新的映射。
声明式UI的优势在于代码更加简洁、可读性更强、逻辑更加清晰。开发者可以将更多的精力放在业务逻辑上,而不是繁琐的DOM操作上。同时,声明式UI也更容易进行单元测试和维护。
在本应用中,几乎所有的UI变化都是通过状态驱动的。Tab切换是通过activeTab状态驱动条件渲染实现的,弹框显示是通过showXxx布尔状态驱动的,动画效果是通过定时器更新状态变量驱动的。这种统一的状态驱动模式使得代码的可预测性大大增强。
10.2 弹框交互的统一模式
应用中共有五个弹框组件,分别是球鞋编辑弹框、配色方案弹框、护理详情弹框、新增定制弹框和删除确认弹框。虽然每个弹框的内容和功能各不相同,但它们都遵循了相同的交互模式。
统一的弹框模式包括以下几个要素:
-
使用
@Builder装饰器定义弹框的构建方法,将弹框的UI封装为一个可复用的构建函数。 -
使用一个布尔类型的
@State状态变量控制弹框的显示和隐藏。状态为true时显示,为false时隐藏。 -
使用Stack堆叠布局,通过条件渲染将弹框叠加在页面内容之上。弹框使用绝对定位铺满整个屏幕,背景是半透明的遮罩层。
-
弹框内容居中显示,使用卡片色背景,带有圆角和边框。
-
弹框通常有标题区、内容区和操作按钮区三个部分。标题区包含标题和关闭按钮,内容区展示具体的表单或信息,操作按钮区提供确认和取消等操作。
-
弹框的数据通过额外的状态变量传递,如编辑弹框的
editShoe、editTarget、editHeat,定制弹框的newName、selSize、selColorIdx等。
这种统一的弹框模式使得整个应用的交互风格保持一致,用户在不同的页面遇到弹框时都能快速理解和操作。同时,统一的模式也降低了开发成本,新增弹框时可以遵循已有的模式快速实现。
10.3 数据可视化的多种实现方式
本应用展示了多种数据可视化的实现方式,包括进度条、柱状图、饼图等。这些可视化组件都是使用基础组件或Canvas API构建的,没有依赖第三方图表库。
进度条是最简单也是使用最多的可视化形式。应用中的进度条都采用了Stack堆叠布局的实现方式:底层是灰色的背景轨道,上层是彩色的进度条。进度条的宽度通过数据值动态计算。
进度条的样式变化丰富:有单色的(如尺码对比条),有渐变色的(如热度进度条);有细的(如人气进度条),有粗的(如尺码对比条);有横向的(大部分进度条),也有纵向的(柱状图本质上也是纵向的进度条)。
柱状图是应用中另一种常见的可视化形式。人气柱状图、工艺单量柱状图、月消费统计柱状图都是柱状图的不同变体。它们的共同特点是使用Column组件作为柱子,通过高度来表示数据值的大小。
不同柱状图的样式各有特色:人气柱状图是纯色的,前三名有颜色区分;工艺柱状图使用了渐变色;月消费柱状图最高的柱子有颜色区分。这些细节上的变化使得每个图表都有自己的视觉特点。
饼图是最复杂的一种可视化形式,使用Canvas API实现。Canvas提供了底层的绘制能力,可以实现任意形状的图形。饼图的绘制涉及路径计算、角度转换、颜色填充等多个步骤,需要一定的图形学知识。
多种数据可视化方式的综合运用,使得应用的数据展示更加丰富和直观。不同的数据类型适合不同的可视化方式:排名类数据适合排行榜+进度条,对比类数据适合柱状图,占比类数据适合饼图。
10.4 动画效果的实现策略
应用中实现了三种不同的动画效果:鞋带摆动动画、喷漆粒子动画和会员卡光泽动画。这三种动画虽然表现形式不同,但都采用了相同的实现策略——“定时器 + 状态变量 + 变换属性”。
具体来说,动画的实现步骤如下:
-
定义一个
@State状态变量,用于存储动画的当前进度(如位移、角度等)。 -
在
aboutToAppear()生命周期钩子中启动一个setInterval定时器,周期性地更新状态变量的值。 -
在
build()方法中,将状态变量的值应用到组件的变换属性(如translate、rotate、opacity等)上。 -
在
aboutToDisappear()生命周期钩子中清除定时器,防止内存泄漏。
这种基于状态驱动的动画实现方式虽然简单,但有一定的局限性。首先,动画的流畅度取决于定时器的间隔和JavaScript的执行效率,可能不如专门的动画API流畅。其次,这种方式需要手动管理定时器和状态,代码量相对较多。
在HarmonyOS ArkTS API 24中,提供了更加完善的动画系统,包括属性动画、显式动画、转场动画等。使用这些动画API可以实现更加流畅、更加丰富的动画效果,同时代码也更加简洁。
不过,对于简单的循环动画,这种"定时器+状态"的方式仍然有其优势:它不需要学习复杂的动画API,逻辑直观易懂,可以精确控制动画的每一个细节。在原型开发或简单动画场景下,这种方式是一种快速有效的选择。
10.5 状态管理的设计思想
状态管理是声明式UI开发中的核心问题。本应用采用了组件内部状态管理的模式,所有的状态都定义在各自的组件内部,通过props和回调函数进行父子组件间的通信。
具体来说,应用中的状态管理遵循以下设计原则:
-
状态就近原则:状态定义在使用它的最近的父组件中。如果一个状态只在某个组件内部使用,就定义在该组件内部。如果多个组件共享一个状态,就将状态提升到它们的共同父组件中。
-
单向数据流:数据从上往下流,事件从下往上传。父组件通过props将数据传递给子组件,子组件通过回调函数将事件通知给父组件。数据的修改权属于持有状态的组件。
-
状态最小化:只定义必要的状态变量,避免冗余的状态。可以通过计算得出的值不要定义为状态,而是在渲染时实时计算。
-
不可变数据:对于对象和数组类型的状态,优先使用不可变的方式更新(如创建新的对象/数组),避免直接修改状态对象的内部属性。虽然ArkTS也支持直接修改数组元素,但不可变数据能更好地追踪状态变化。
在本应用中,Tab切换状态(activeTab)定义在根组件中,因为它被底部Tab栏和内容区共享。各Tab内部的状态(如筛选条件、弹框显隐、输入数据等)则定义在各自的Tab组件内部。
父子组件通信方面,首页Tab通过onGoTab回调函数通知父组件切换Tab。弹框的状态由父组件管理,弹框本身只是一个纯展示的@Builder方法,通过闭包访问父组件的状态和方法。
这种轻量级的状态管理方式对于中小型应用来说是足够的。当应用规模扩大、状态共享变得更加复杂时,可以考虑使用更高级的状态管理方案,如AppStorage、PersistentStorage或第三方状态管理库。
十一、组件级技术细节深度剖析
11.1 Grid组件与列表渲染机制深度分析
Grid组件是ArkUI中用于二维网格布局的核心组件。在本应用中,Grid被广泛用于功能导航宫格、潮流色卡、球鞋商品展示等多个场景。不同场景下Grid的列数、间距、高度等参数各有不同,但底层的渲染机制是一致的。
Grid组件通过columnsTemplate属性定义列的数量和宽度分配。1fr表示一个弹性单位,所有列的fr值之和为总宽度,每列按比例分配。例如'1fr 1fr 1fr'表示三列等宽布局,每列占据总宽度的三分之一。
Grid的行高可以通过rowsTemplate属性显式定义,也可以由内容自动撑开。在本应用中,大多数Grid都没有设置rowsTemplate,而是通过设置Grid的总高度来间接控制行高。这种方式适用于行数固定且每行高度一致的场景。
Grid组件内部使用GridItem子组件来包裹每个网格项的内容。GridItem可以设置rowStart、rowEnd、columnStart、columnEnd等属性来实现跨行或跨列布局。在本应用中,所有GridItem都是单格布局,没有使用跨行跨列的特性。
Grid的性能优化是一个值得关注的话题。对于大量数据的网格布局,Grid组件本身提供了一定的回收复用机制,但开发者也可以通过一些最佳实践来进一步提升性能。
首先,确保ForEach的键生成函数返回唯一且稳定的键。键是框架识别列表项的重要依据,唯一稳定的键可以最大程度地减少不必要的组件重建和DOM操作。在本应用中,球鞋Grid的键包含了品类信息,这是因为品类切换时数据会完全变化,通过改变键可以强制框架重建组件,避免状态残留。
其次,避免在Grid的构建函数中执行复杂的计算。每次状态变化触发重新渲染时,构建函数都会被重新执行,如果其中包含耗时的计算,会影响渲染性能。对于复杂的计算结果,可以考虑使用计算属性或将计算逻辑移到状态变更的地方。
第三,合理设置Grid的高度。如果Grid的高度是通过内容自动计算的,框架需要先测量所有子组件的高度才能确定Grid的高度,这会增加布局计算的开销。如果能提前知道Grid的高度(如本应用中的动态高度计算),显式设置高度可以减少一次布局计算。
11.2 Scroll组件与滚动交互机制
Scroll组件是ArkUI中实现可滚动内容的基础组件。本应用的每个Tab页都使用了Scroll组件包裹内容,确保在内容超出屏幕高度时可以滚动查看。
Scroll组件的基本使用非常简单:将需要滚动的内容作为Scroll的子组件,设置滚动方向和滚动条显示模式即可。在本应用中,所有的Scroll都设置为垂直滚动方向(默认值),并且隐藏了滚动条(scrollBar(BarState.Off))。
隐藏滚动条是移动端应用的常见设计。移动端的滚动通常通过手指滑动来操作,滚动条的存在更多是视觉上的干扰。隐藏滚动条可以让界面更加简洁,但也需要确保用户能够感知到内容是可滚动的。
Scroll组件提供了丰富的滚动控制能力,包括滚动到指定位置、滚动到边缘、监听滚动事件等。在本应用中,没有使用这些高级功能,Scroll仅仅作为内容的滚动容器使用。
Scroll组件的边缘效果是另一个值得关注的特性。在HarmonyOS中,Scroll默认提供了弹性滚动效果,当滚动到边缘时继续拉动会有弹性回弹的效果。这种效果提升了滚动的手感和趣味性,是HarmonyOS UI的标志性特性之一。
在性能方面,Scroll组件内置了内容回收机制。对于长列表,Scroll会回收不可见区域的组件,只保留可见区域的渲染,从而大大减少内存占用和渲染开销。不过,这种回收机制通常需要配合List组件或特定的使用方式才能生效。
在本应用中,由于每个Tab的内容量不算特别大,Scroll的性能压力相对较小。但如果内容量大幅增加,就需要考虑使用更高效的列表组件来优化滚动性能。
11.3 Stack布局与层级管理原理
Stack布局是ArkUI中实现堆叠效果的核心布局组件。在本应用中,Stack被广泛用于各种需要元素叠加的场景,如进度条、标签叠加、弹框遮罩、图标徽章等。
Stack布局的核心特点是子组件按照添加顺序从上到下堆叠,后添加的子组件会覆盖在先添加的子组件之上。每个子组件可以通过alignContent属性控制其在Stack中的对齐方式,如顶部对齐、底部对齐、居中等。
Stack的一个重要属性是zIndex,它可以显式控制组件的堆叠顺序。默认情况下,zIndex为0,组件按添加顺序堆叠。通过设置更高的zIndex,可以让组件显示在更上层。在本应用中,弹框遮罩层设置了zIndex(999),确保弹框始终显示在所有内容的最上方。
Stack布局的另一个重要特性是position属性。通过position属性可以精确控制子组件在Stack中的位置,支持x和y坐标偏移。这种绝对定位的方式在实现一些特殊的视觉效果时非常有用,如通知红点、粒子动画等。
在本应用中,进度条是Stack最典型的应用场景。进度条的底层是灰色的背景轨道,上层是彩色的进度条。两层都使用Column组件,通过Stack堆叠在一起。上层进度条的宽度根据进度值动态计算,从而实现进度条的视觉效果。
弹框遮罩是Stack的另一个典型应用。弹框使用半透明的黑色背景铺满整个屏幕,在屏幕中央显示弹框内容。遮罩层既起到了视觉强调的作用,也可以拦截用户对下方内容的操作。
Stack布局虽然功能强大,但也需要谨慎使用。过多的Stack嵌套会增加布局计算的复杂度,影响渲染性能。在可以使用其他布局实现的情况下,优先使用更简单的布局方式。
11.4 Slider组件与手势交互深度解析
Slider组件是ArkUI中用于范围选择的交互组件。在球鞋Tab的编辑参数弹框中,使用了两个Slider组件分别用于调节目标价格和热度阈值。
Slider组件的基本配置包括当前值、最小值、最大值、步长和样式。步长(step)决定了滑块的调节精度,设置合适的步长可以让用户更精确地选择数值。在本应用中,价格滑块的步长为50,热度滑块的步长为5,分别对应不同的精度需求。
Slider的样式有两种可选:OutSet和InSet。OutSet样式的滑块按钮在轨道外侧,InSet样式的滑块按钮在轨道内侧。本应用使用了OutSet样式,这种样式的滑块更大、更容易操作,适合移动端的触控交互。
Slider的视觉配置包括选中轨道颜色、未选中轨道颜色和滑块按钮颜色。在本应用中,两个Slider的颜色配置正好相反:价格滑块的选中轨道是主色调,滑块按钮是强调色;热度滑块的选中轨道是强调色,滑块按钮是主色调。这种颜色互换的设计既区分了两个滑块的功能,又保持了整体的配色协调。
Slider的交互通过onChange事件回调来处理。当用户拖动滑块时,onChange会被频繁触发,每次触发时传入当前的滑块值。在本应用中,每次值变化都会立即更新到对应的@State状态变量中,从而触发UI的实时更新。用户可以在拖动滑块的同时看到数值的变化,获得即时的视觉反馈。
Slider组件的手势交互设计非常精细。它支持拖拽操作,有最小滑块区域的触控检测,有滑块回弹的动画效果。这些细节虽然用户不一定能明确说出,但会在潜意识中影响用户对应用"好用"与否的判断。
在性能方面,Slider的频繁值变化可能会导致大量的重新渲染。如果Slider控制的UI变化很复杂,可能会影响拖动的流畅度。在这种情况下,可以考虑使用节流(throttle)或防抖(debounce)技术来减少渲染次数。但在本应用中,由于只是更新一个Text组件的数值,性能开销很小,不需要额外优化。
11.5 TextInput组件与表单状态管理
TextInput组件是ArkUI中用于文本输入的基础组件。在客制Tab的新增定制弹框中,使用了TextInput组件让用户输入鞋款名称。
TextInput组件的基本配置包括占位符(placeholder)、初始值(text)、输入类型等。占位符在用户输入前显示提示文字,引导用户输入正确的内容。占位符的颜色和字体可以单独配置,确保与实际输入的文字有所区分。
在本应用中,TextInput的样式经过了精心设计:高度为40像素,深卡片色背景,10像素圆角,左右各12像素的内边距。这些样式参数使得输入框看起来舒适且易于操作,符合移动端的设计规范。
TextInput的输入状态通过onChange事件回调来监听。每次用户输入时,onChange都会被触发,传入当前输入框中的完整文本。在本应用中,输入的文本被实时更新到newName状态变量中,实现了双向数据绑定的效果。
表单状态管理是一个重要的话题。在包含多个输入项的表单中,如何有效地管理表单数据、验证用户输入、处理表单提交,是开发者需要面对的问题。
在本应用中,新增定制弹框是一个简单的表单,包含一个文本输入、一组尺码选择按钮和一组配色选择按钮。表单的状态分散在三个状态变量中:newName存储文本输入值,selSize存储选中的尺码,selColorIdx存储选中的配色索引。
这种分散的状态管理方式对于简单表单来说是可行的,但对于复杂表单可能会变得难以维护。在更复杂的场景下,可以考虑将表单数据组织成一个对象,或者使用专门的表单状态管理方案。
表单验证是另一个重要的方面。在本应用中,没有实现输入验证的逻辑,用户可以输入任意内容甚至空内容后直接提交。在实际应用中,需要对用户输入进行验证,如检查必填项是否为空、检查输入格式是否正确、检查数值是否在合理范围内等。
11.6 Canvas 2D绘制API深度解析
Canvas是ArkUI提供的强大自定义绘制能力,通过Canvas 2D API,开发者可以在画布上绘制任意形状的图形、文字和图片。在配色Tab中,Canvas被用于绘制色系分布饼图。
Canvas的使用需要两个关键步骤:创建渲染上下文和执行绘制命令。渲染上下文(CanvasRenderingContext2D)是所有绘制操作的入口,它提供了丰富的绘制方法,如画线、画弧、填充、描边等。
在本应用中,Canvas渲染上下文在组件的成员变量中创建,传入一个RenderingContextSettings对象来配置渲染设置。RenderingContextSettings的构造参数是一个布尔值,表示是否开启抗锯齿。开启抗锯齿可以让图形的边缘更加平滑,提升视觉质量。
绘制操作在Canvas的onReady回调中执行。onReady回调在Canvas组件准备就绪、可以开始绘制时触发。这是开始绘制的最佳时机,因为此时Canvas的尺寸和上下文都已经准备好。
饼图的绘制涉及多个Canvas API的使用:
beginPath()方法用于开始一条新的路径。每次绘制新的图形之前,都应该调用beginPath()来清除之前的路径,否则新的绘制会和之前的路径叠加在一起。
moveTo()方法用于移动绘制起点到指定坐标。在绘制扇形时,需要先移动到圆心位置,然后再画圆弧。
arc()方法用于绘制圆弧。它接收六个参数:圆心x坐标、圆心y坐标、半径、起始弧度、结束弧度、是否逆时针。在饼图绘制中,通过计算每个扇形的起始角度和结束角度,绘制出对应的圆弧。
closePath()方法用于闭合当前路径。它会从当前点画一条直线回到路径的起点,形成一个闭合的形状。在扇形绘制中,closePath()会将圆弧的终点与圆心连接起来,形成一个完整的扇形。
fillStyle属性用于设置填充颜色。设置后,后续的fill()操作会使用这个颜色填充路径。在饼图中,每个扇形使用不同的颜色填充,因此需要在每次填充前设置对应的fillStyle。
fill()方法用于填充当前路径所围成的区域。它会使用当前的fillStyle颜色填充路径内部。
strokeStyle属性用于设置描边颜色。设置后,后续的stroke()操作会使用这个颜色描边。
lineWidth属性用于设置描边线宽。线宽越大,描边越粗。
stroke()方法用于描边当前路径。它会使用当前的strokeStyle颜色和lineWidth线宽沿着路径绘制线条。
通过这些基础API的组合使用,可以绘制出非常复杂的图形。Canvas的强大之处在于它的灵活性,开发者可以精确控制每一个像素的绘制。但相应地,Canvas也需要开发者手动处理交互、动画等逻辑,相比组件化开发更加底层。
11.7 @Builder装饰器与UI复用模式
@Builder装饰器是ArkTS中一种特殊的函数装饰器,它允许将UI构建逻辑封装为一个可复用的函数。@Builder装饰的函数可以像组件一样在build()方法中调用,返回构建好的UI元素。
在本应用中,所有的弹框组件都使用@Builder装饰器定义。每个弹框被封装为一个构建方法(modalOverlay),在需要显示弹框的地方调用这个方法即可。
使用@Builder的好处在于:
第一,代码组织更加清晰。将复杂的UI片段抽取为独立的构建函数,可以让主build()方法更加简洁,可读性更强。
第二,支持条件渲染。通过if语句结合@Builder函数,可以实现UI的条件显示,如弹框的显示和隐藏。
第三,可以接收参数。@Builder函数可以接收参数,根据不同的参数构建不同的UI。虽然本应用中的弹框构建函数没有显式参数(通过闭包访问组件状态),但@Builder本身是支持参数传递的。
第四,可以在多个地方复用。同一个@Builder函数可以在多个位置被调用,实现UI的复用。
当然,@Builder也有其局限性。首先,@Builder函数是与组件绑定的,不能在不同组件之间复用。如果需要跨组件复用,应该定义为自定义组件。
其次,@Builder函数内部不能定义状态变量,它只能使用所在组件的状态。这意味着@Builder函数本质上是组件的一部分,而不是独立的实体。
第三,@Builder函数的性能与内联的UI构建代码相同,没有额外的开销。因为在编译阶段,@Builder函数的内容会被内联到调用位置。
在本应用中,弹框使用@Builder而不是独立组件的方式,主要是因为弹框与父组件共享了大量的状态。如果将弹框抽取为独立组件,需要通过props传递大量数据,通过事件回调通知父组件,反而增加了复杂度。
但从可复用性和可维护性的角度来看,对于通用的弹框组件(如确认对话框、加载提示等),还是应该抽取为独立的自定义组件,以便在整个应用中复用。
11.8 ForEach组件与列表渲染优化策略
ForEach组件是ArkTS中实现列表渲染的核心组件。它可以根据数据数组动态生成一组UI元素,是声明式UI中数据驱动视图的典型代表。
在本应用中,ForEach被广泛用于各种列表场景,包括球鞋列表、配色列表、护理产品列表、定制方案列表、订单列表、收藏列表等。几乎所有的列表展示都使用了ForEach组件。
ForEach的基本用法接收三个参数:第一个是数据数组,第二个是子组件构建函数,第三个是键生成函数。
数据数组是列表数据的来源。ForEach会遍历数组中的每个元素,为每个元素调用构建函数生成对应的子组件。
子组件构建函数接收两个参数:当前遍历的数组元素和当前索引。构建函数返回一个组件,作为该数组元素对应的列表项。
键生成函数接收当前数组元素,返回一个字符串类型的键。键是ForEach性能优化的关键,框架通过键来识别列表项的身份,从而决定是更新、移动还是重建组件。
键的设计是列表性能优化的核心。一个好的键应该满足以下条件:
第一,唯一性。每个列表项的键必须是唯一的,不能有重复。如果有重复的键,框架可能无法正确识别列表项,导致渲染错误。
第二,稳定性。同一个列表项的键应该保持稳定,不随渲染次数而变化。如果键不稳定,框架会认为列表项发生了变化,导致不必要的重建。
第三,相关性。键应该与数据的身份相关,而不是与数据的位置相关。例如,使用数据的id作为键比使用索引作为键更好,因为当列表顺序发生变化时,id仍然能正确标识数据的身份。
在本应用中,所有的ForEach都提供了键生成函数,并且键都是基于数据的id生成的。这是一种很好的实践,确保了列表的渲染性能和正确性。
有些ForEach的键中还包含了额外的信息,如球鞋Grid的键包含了品类信息。这是因为品类切换时,列表中的数据会完全变化,通过在键中加入品类信息,可以强制框架重建所有列表项,避免旧数据的状态残留。
ForEach的性能优化还包括以下几个方面:
首先,避免在构建函数中创建新的函数或对象。每次渲染都创建新的闭包会增加垃圾回收的压力。可以将事件处理函数定义为组件的方法,而不是在构建函数中内联定义。
其次,合理使用组件复用。ForEach会尽可能复用组件实例,但如果组件的结构变化太大,复用的效果会打折扣。保持列表项组件的结构稳定,可以提升ForEach的复用效率。
第三,对于超长列表,考虑使用List组件代替ForEach+Scroll的组合。List组件内置了更高效的回收复用机制,可以在数据量很大时保持良好的滚动性能。
11.9 渲染流程图详解
十二、各模块特性对比总结
12.1 六Tab核心特性对比表
| 对比维度 | 首页Tab | 球鞋Tab | 配色Tab | 护理Tab | 客制Tab | 我的Tab |
|---|---|---|---|---|---|---|
| 核心功能 | 内容聚合与导航 | 商品浏览与筛选 | 色彩探索与选择 | 护理产品展示 | 定制服务介绍 | 个人中心管理 |
| 主要模块数 | 4个 | 4个 | 4个 | 2个 | 3个 | 4个 |
| 状态变量数 | 1个 | 5个 | 4个 | 2个 | 4个 | 4个 |
| 是否有动画 | 是(鞋带摆动) | 否 | 是(喷漆粒子) | 否 | 否 | 是(光泽扫过) |
| 是否有弹框 | 否 | 是(编辑参数) | 是(方案选择) | 是(产品详情) | 是(新增定制) | 是(删除确认) |
| 弹框类型 | - | 参数设置型 | 选择确认型 | 信息展示型 | 表单提交型 | 警告确认型 |
| 弹框主色调 | - | 暗夜紫 | 电光绿 | 电光绿 | 暗夜紫 | 危险红 |
| 数据可视化 | 热度进度条 | 进度条+柱状图 | 饼图+进度条+柱状图 | 进度条 | 进度条+柱状图 | 柱状图 |
| 图表类型 | 横向进度条 | 横向进度条 + 纵向柱状图 | 饼图 + 横向进度条 | 横向进度条 | 横向进度条 + 纵向柱状图 | 纵向柱状图 |
| 列表形式 | 横滑卡片 + 排行榜 | 双列Grid + 列表 | 三列Grid + 列表 | 排行榜列表 | 横向卡片列表 | 订单列表 + 收藏列表 |
| 筛选功能 | 否 | 是(品类筛选) | 否 | 否 | 否 | 否 |
| 表单输入 | 否 | 滑块输入 | 色卡选择 | 否 | 文本输入 + 选择 | 否 |
| 删除操作 | 否 | 否 | 否 | 否 | 否 | 是(收藏删除) |
| Canvas使用 | 否 | 否 | 是(饼图) | 否 | 否 | 否 |
| 定时器使用 | 是 | 否 | 是 | 否 | 否 | 是 |
| 生命周期钩子 | 有 | 无 | 有 | 无 | 无 | 有 |
| 数据操作 | 只读 | 只读+编辑参数 | 只读+选择配色 | 只读 | 只读+提交表单 | 读+删 |
| 交互复杂度 | 中等 | 较高 | 较高 | 中等 | 较高 | 高 |
12.2 三种动画效果对比表
| 对比维度 | 鞋带摆动动画 | 喷漆粒子动画 | 会员卡光泽动画 |
|---|---|---|---|
| 所在页面 | 首页Tab | 配色Tab | 我的Tab |
| 动画类型 | 上下摆动 | 下落消散 | 水平扫过 |
| 状态变量 | snLaceY | snSprayY | snShine |
| 定时器间隔 | 50ms | 40ms | 40ms |
| 每次增量 | 2 | 5 | 6 |
| 循环周期 | 40(取模) | 160(取模) | 420 ~ -80(条件重置) |
| 动画元素数 | 9条鞋带 | 8个粒子 | 1个光条 |
| 变换属性 | translateY(translate) | translateY + opacity | translateX(translate) + rotate |
| 视觉效果 | 波浪式递进摆动 | 粒子下落渐隐 | 斜向光泽扫过 |
| 实现复杂度 | 中等 | 较高 | 中等 |
| 性能影响 | 较低 | 中等 | 较低 |
| 氛围营造 | 动态活力感 | 创作过程感 | 高端质感 |
12.3 五种弹框模式对比表
| 对比维度 | 编辑参数弹框 | 配色方案弹框 | 护理详情弹框 | 新增定制弹框 | 删除确认弹框 |
|---|---|---|---|---|---|
| 所在Tab | 球鞋Tab | 配色Tab | 护理Tab | 客制Tab | 我的Tab |
| 弹框类型 | 参数设置 | 选择确认 | 信息展示 | 表单提交 | 警告确认 |
| 触发方式 | 点击球鞋卡片 | 点击方案选择 | 点击产品项 | 点击发起定制 | 点击删除按钮 |
| 内容区组件 | 滑块x2 | Grid色卡 | 参数表x7行 | 输入框+选择+色卡 | 警告图标+文字 |
| 操作按钮数 | 2个 | 2个 | 1个 | 1个 | 2个 |
| 确认按钮文字 | 保存参数 | 应用方案 | 加入护理清单 | 提交定制单 | 确认删除 |
| 确认按钮颜色 | 主色调紫 | 强调色绿 | 强调色绿 | 强调色绿 | 危险色红 |
| 取消按钮 | 有(重置) | 有 | 无 | 无 | 有 |
| 关闭按钮 | 无 | 有 | 有 | 有 | 无 |
| 弹框宽度 | 86% | 88% | 86% | 88% | 80% |
| 边框颜色 | 分割线色 | 分割线色 | 分割线色 | 深主色调 | 半透明危险色 |
| 状态变量数 | 3个(数据)+1个(显隐) | 2个(数据)+1个(显隐) | 1个(数据)+1个(显隐) | 3个(数据)+1个(显隐) | 1个(数据)+1个(显隐) |
| 交互复杂度 | 高 | 中等 | 低 | 高 | 中等 |
十三、综合总结与技术展望
13.1 技术架构维度总结
从技术架构的角度来看,这个球鞋定制工坊应用展示了一个完整的ArkTS单文件应用的架构模式。整个应用采用了分层的架构设计:数据层位于最底层,包含枚举、接口、常量和辅助函数;组件层位于上层,包含入口组件和六个功能Tab组件。
数据层的设计体现了类型安全和数据驱动的思想。通过接口定义明确了数据的形状和类型,通过常量集中管理所有的业务数据,通过辅助函数封装了通用的数据处理逻辑。这种设计使得数据层独立于UI层,具有良好的可维护性和可测试性。
组件层的设计遵循了声明式UI的开发范式。每个组件都是一个独立的、自包含的单元,有自己的状态和UI描述。组件之间通过props和回调函数进行通信,形成了单向的数据流。这种设计使得组件的职责清晰、边界明确,便于理解和维护。
入口组件作为应用的根组件,承担了全局布局和导航的职责。它管理着Tab切换的全局状态,协调各Tab组件的显示和隐藏。底部Tab栏作为主导航入口,提供了页面间的快速切换。
弹框组件采用了@Builder的方式定义,与父组件共享状态。这种设计虽然简单直接,但也存在弹框与父组件耦合度高、难以复用的问题。在更复杂的应用中,可以考虑将弹框封装为独立的自定义组件,通过props和事件进行通信。
总体而言,应用的架构设计简洁清晰、层次分明,符合ArkTS声明式开发的最佳实践。对于中小型应用来说,这种架构是高效且合适的。
13.2 视觉设计维度总结
从视觉设计的角度来看,这个应用成功地营造了赛博朋克风格的暗色主题视觉效果。炭黑、暗夜紫、电光绿的三色搭配既具有强烈的视觉冲击力,又保持了良好的可读性和层次感。
应用的视觉设计具有以下几个显著特点:
第一,系统化的配色体系。应用定义了完整的调色板,包含背景色、卡片色、文字色、主色调、强调色、状态色等十三种颜色变量。所有的颜色使用都通过引用调色板变量来实现,确保了配色的一致性和可维护性。
第二,丰富的渐变效果。应用大量使用了线性渐变来增强视觉层次感。从卡片背景到商品色块,从进度条到柱状图,渐变效果无处不在。渐变不仅让界面更加美观,也增加了界面的深度和质感。
第三,统一的圆角设计。应用中的卡片、按钮、标签、进度条等元素都使用了圆角设计,圆角的大小根据元素的尺寸和重要性有所不同。统一的圆角设计赋予了界面柔和、现代的视觉风格。
第四,清晰的文字层级。应用中的文字分为三个层级:主文字色用于标题和重要内容,次文字色用于正文和辅助信息,辅助文字色用于次要说明和禁用状态。三级文字色体系确保了信息层级的清晰表达。
第五,动态的视觉效果。应用中的三个动画效果(鞋带摆动、喷漆粒子、光泽扫过)为静态的界面增添了动感和活力,提升了用户的视觉体验。
总体而言,应用的视觉设计在美学和功能性之间取得了良好的平衡。赛博风的主题不仅符合球鞋定制的潮流定位,也通过系统化的设计确保了界面的可用性和可读性。
13.3 交互设计维度总结
从交互设计的角度来看,这个应用遵循了移动端应用的常见交互模式,同时也有一些自己的特色。
Tab导航是应用的主导航模式,底部Tab栏提供了六个页面的快速切换。这种导航模式在移动端应用中非常普遍,用户学习成本低,操作效率高。Tab切换时页面会完整重建,虽然牺牲了一些性能,但实现简单,对于内容不太复杂的页面是可以接受的。
弹框交互是应用中重要的交互模式,五种不同类型的弹框覆盖了参数设置、选择确认、信息展示、表单提交和警告确认等多种场景。统一的弹框设计模式确保了交互体验的一致性。
列表和Grid是内容展示的主要形式。应用中使用了多种列表布局,包括横向滚动列表、纵向排行榜列表、双列商品Grid、三列色卡Grid等。不同的布局形式适用于不同的内容类型和展示需求。
筛选和排序是商品浏览的重要交互。球鞋Tab提供了品类筛选功能,通过顶部的分类标签快速切换不同品类的商品。各种排行榜则提供了按热度、功效等维度排序的内容展示。
表单输入是定制功能的核心交互。新增定制弹框提供了文本输入、尺码选择、配色选择等多种表单控件。滑块控件用于参数的精细调节,提供了直观的数值反馈。
删除操作采用了二次确认的交互模式,通过警告弹框防止用户误操作。这种设计体现了对用户数据的尊重和保护。
总体而言,应用的交互设计简洁直观、符合用户习惯,同时也有足够的丰富性来满足不同功能的需求。
13.4 性能优化维度总结
从性能优化的角度来看,这个应用虽然是一个原型级的应用,但在一些方面也体现了性能优化的意识。
首先,不可变数据的处理方式。在排序等可能修改原数组的操作中,应用先创建数组的浅拷贝,然后对拷贝进行排序,避免了修改原始数据。这种不可变数据的处理方式虽然主要是为了数据安全,但也有助于框架追踪数据变化,从而优化渲染性能。
其次,ForEach的键生成函数。应用中所有的ForEach都提供了键生成函数,为每个列表项生成唯一的键。这对于框架的列表更新优化非常重要。有了唯一键,框架可以准确地识别哪些元素是新增的、哪些是删除的、哪些是移动的,从而最小化DOM操作,提升列表更新的性能。
第三,条件渲染的使用。Tab切换使用条件渲染而非显示/隐藏切换,虽然每次切换都会重建组件,但也意味着不显示的Tab不会占用内存和渲染资源。对于内容较重且切换不频繁的Tab来说,条件渲染可能比保持所有Tab实例更节省资源。
第四,Scroll组件的使用。所有的长列表都使用了Scroll组件包裹,确保内容可以滚动查看。Scroll组件内置了滚动优化和回收机制,可以在内容较长时保持良好的滚动性能。
当然,作为一个原型应用,还有很多性能优化的空间。例如:
-
定时器动画可以替换为属性动画或显式动画,获得更好的性能和更丰富的动画效果。
-
辅助函数中的重复计算(如每次渲染都重新计算最大值)可以通过计算属性或缓存来优化。
-
弹框组件可以使用懒加载或预创建的方式优化弹框弹出的速度。
-
对于大量数据的列表,可以使用List组件代替ForEach+Scroll的组合,获得更好的滚动性能和内存使用效率。
总体而言,应用在基本的性能方面做得不错,但在更高级的性能优化方面还有提升空间。对于原型级的应用来说,当前的性能表现是足够的。
13.5 代码质量维度总结
从代码质量的角度来看,这个应用的代码具有以下几个优点:
第一,命名规范统一。变量、函数、组件的命名都遵循了统一的命名规范。组件使用大驼峰命名法,变量和函数使用小驼峰命名法,常量使用全大写下划线分隔法。清晰的命名使得代码具有很好的可读性。
第二,代码结构清晰。文件中的内容按照枚举、接口、主题配置、数据常量、辅助函数、入口组件、各Tab组件的顺序组织,层次分明。每个部分之间都有注释分隔符,便于快速定位。
第三,注释适度。代码中有适量的注释,包括模块说明、代码段说明等。注释既不会多到干扰代码阅读,也不会少到让人无法理解代码的意图。
第四,代码复用。应用中通过辅助函数封装了常用的数据处理逻辑,通过统一的设计模式确保了交互方式的一致性。虽然没有抽取公共组件,但相似的设计模式本身也是一种复用。
第五,类型安全。应用充分利用了TypeScript/ArkTS的类型系统,为所有的数据结构定义了接口,为函数参数和返回值定义了类型。这大大减少了类型相关的bug,提升了代码的健壮性。
当然,代码也有一些可以改进的地方:
-
部分代码存在重复。例如底部Tab栏的六个Tab项、尺码选择的七个按钮等,都是类似的代码重复。可以考虑使用ForEach循环来减少重复代码。
-
弹框代码与父组件耦合。每个弹框都定义在父组件内部,作为一个@Builder方法,导致弹框无法在其他组件中复用。可以考虑抽取为独立的自定义组件。
-
魔法数字较多。代码中有一些硬编码的数值,如动画参数、尺寸数值等。可以考虑抽取为常量,提高代码的可维护性。
-
缺少错误处理。目前代码中几乎没有错误处理逻辑,如数据为空、边界情况等都没有特殊处理。在生产环境中,需要增加完善的错误处理和边界情况处理。
总体而言,应用的代码质量较高,结构清晰、命名规范、类型安全,具有很好的可读性和可维护性。
安装DevEco Studio程序

选择目标安装目录:

设置环境变量,但是需要重启一下:

新建一个空白模板:

设置API为24的模板项目:
初始化项目,自动下载相关依赖:

完整代码:
// ============================================================
// SNEAK LAB 球鞋定制工坊
// 主题:炭黑 #16161A / 暗夜紫 #7C4DFF / 电光绿 #B6FF00
// 暗色潮流赛博风 · 单文件 ArkTS 应用
// ============================================================
// ============ 枚举 ============
enum SNTab {
Home,
Shoes,
Color,
Care,
Custom,
Mine
}
// ============ 类型定义 ============
interface SNShoe {
id: number
name: string
cat: string
price: number
oldPrice: number
heat: number
colorHex: string
limited: boolean
tag: string
}
interface SNColorItem {
id: number
name: string
colorHex: string
series: string
heat: number
}
interface SNCareProduct {
id: number
name: string
effect: string
power: number
usage: number
price: number
volume: string
material: string
}
interface SNCustomPlan {
id: number
name: string
craft: string
days: number
price: number
popular: number
desc: string
}
interface SNOrderItem {
id: number
name: string
status: string
date: string
price: number
colorHex: string
}
interface SNFavoriteItem {
id: number
name: string
price: number
note: string
colorHex: string
}
interface SNSpendItem {
id: number
month: string
amount: number
}
interface SNFuncItem {
id: number
icon: string
name: string
tab: SNTab
}
interface SNCraftStat {
id: number
name: string
count: number
}
interface SNColorPalette {
bg: string
card: string
cardDeep: string
line: string
primary: string
primaryDeep: string
accent: string
textMain: string
textSub: string
textDim: string
danger: string
warn: string
}
// ============ 主题配置 ============
const SN_COLORS: SNColorPalette = {
bg: '#16161A',
card: '#1F1F26',
cardDeep: '#232331',
line: '#2E2E3A',
primary: '#7C4DFF',
primaryDeep: '#4A2B99',
accent: '#B6FF00',
textMain: '#F2F2F7',
textSub: '#A8A8B3',
textDim: '#6E6E7A',
danger: '#FF4D5E',
warn: '#FFB300'
}
// ============ 数据 ============
const SN_SHOES: SNShoe[] = [
{ id: 1, name: '幻影紫芒 Runner X1', cat: '跑步', price: 1299, oldPrice: 1599, heat: 96, colorHex: '#7C4DFF', limited: true, tag: '新品' },
{ id: 2, name: '电光绿锋 Bolt EZ', cat: '跑步', price: 1099, oldPrice: 1299, heat: 88, colorHex: '#B6FF00', limited: false, tag: '热卖' },
{ id: 3, name: '炭黑武士 Court Dark', cat: '篮球', price: 1399, oldPrice: 1699, heat: 92, colorHex: '#3A3A46', limited: false, tag: '经典' },
{ id: 4, name: '荧光夜巡 Night Patrol', cat: '滑板', price: 899, oldPrice: 999, heat: 76, colorHex: '#FFE14D', limited: false, tag: '潮玩' },
{ id: 5, name: '白紫交响 Symphony Low', cat: '休闲', price: 999, oldPrice: 1199, heat: 84, colorHex: '#EDE7FF', limited: false, tag: '百搭' },
{ id: 6, name: '熔岩橙暴 Blaze High', cat: '篮球', price: 1599, oldPrice: 1899, heat: 90, colorHex: '#FF6D3B', limited: true, tag: '限量' },
{ id: 7, name: '冰蓝极光 Aurora Mid', cat: '休闲', price: 1199, oldPrice: 1399, heat: 71, colorHex: '#4FC3F7', limited: false, tag: '清爽' },
{ id: 8, name: '暗夜潜行者 Shadow Pro', cat: '跑步', price: 1899, oldPrice: 2199, heat: 95, colorHex: '#26262E', limited: true, tag: '顶级' },
{ id: 9, name: '复古灰调 Retro Grey', cat: '滑板', price: 799, oldPrice: 899, heat: 65, colorHex: '#9E9EA6', limited: false, tag: '复古' },
{ id: 10, name: '全息幻彩 Holo Dream', cat: '休闲', price: 1699, oldPrice: 1999, heat: 89, colorHex: '#D9E4FF', limited: true, tag: '联名' }
]
const SN_COLORS_LIST: SNColorItem[] = [
{ id: 1, name: '暗夜紫', colorHex: '#7C4DFF', series: '紫系', heat: 95 },
{ id: 2, name: '电光绿', colorHex: '#B6FF00', series: '荧光', heat: 92 },
{ id: 3, name: '炭晶黑', colorHex: '#26262E', series: '暗色', heat: 88 },
{ id: 4, name: '樱花粉', colorHex: '#FF6EC7', series: '粉系', heat: 81 },
{ id: 5, name: '冰川蓝', colorHex: '#4FC3F7', series: '蓝系', heat: 85 },
{ id: 6, name: '熔岩橙', colorHex: '#FF6D3B', series: '暖系', heat: 78 },
{ id: 7, name: '幻彩银', colorHex: '#D9E4FF', series: '中性', heat: 73 },
{ id: 8, name: '荧光黄', colorHex: '#FFE14D', series: '荧光', heat: 69 }
]
const SN_CARES: SNCareProduct[] = [
{ id: 1, name: '中底去黄剂', effect: '还原中底白度', power: 95, usage: 34, price: 89, volume: '120ml', material: '橡胶中底' },
{ id: 2, name: '深层清洁泡沫', effect: '瓦解顽固污渍', power: 92, usage: 46, price: 69, volume: '200ml', material: '网面/织物' },
{ id: 3, name: '防水防污喷雾', effect: '形成纳米防护膜', power: 88, usage: 28, price: 79, volume: '150ml', material: '全鞋面' },
{ id: 4, name: '除臭杀菌球', effect: '抑菌除味 30 天', power: 81, usage: 12, price: 39, volume: '一对装', material: '鞋内腔' },
{ id: 5, name: '皮质滋养油', effect: '防裂增亮护理', power: 76, usage: 18, price: 59, volume: '80ml', material: '头层皮革' },
{ id: 6, name: '鞋带增白液', effect: '快速焕白鞋带', power: 70, usage: 8, price: 29, volume: '100ml', material: '棉质鞋带' },
{ id: 7, name: '麂皮橡皮擦', effect: '干式去污不留痕', power: 68, usage: 10, price: 25, volume: '两块装', material: '麂皮/翻毛' },
{ id: 8, name: '网面除尘刷', effect: '日常快速除尘', power: 62, usage: 22, price: 19, volume: '软毛刷', material: '网面鞋身' }
]
const SN_CUSTOMS: SNCustomPlan[] = [
{ id: 1, name: '全鞋手绘喷漆', craft: '喷绘', days: 15, price: 899, popular: 94, desc: '整鞋渐变喷绘 + 保护清漆' },
{ id: 2, name: '后跟刺绣定制', craft: '刺绣', days: 7, price: 399, popular: 88, desc: '姓名/图案激光定位刺绣' },
{ id: 3, name: '皮革激光雕花', craft: '激光', days: 10, price: 659, popular: 82, desc: '激光切割镂空个性花纹' },
{ id: 4, name: '鞋带手工编织', craft: '编织', days: 5, price: 199, popular: 76, desc: '双色编织替换鞋带系统' },
{ id: 5, name: '中底个性彩绘', craft: '中底', days: 9, price: 529, popular: 71, desc: '中底文字涂鸦 + 防水封层' },
{ id: 6, name: '全片热转印', craft: '热转印', days: 12, price: 459, popular: 66, desc: '高清图案全片热转印' }
]
const SN_ORDERS: SNOrderItem[] = [
{ id: 1, name: '幻影紫芒 Runner X1', status: '已发货', date: '08-24', price: 1299, colorHex: '#7C4DFF' },
{ id: 2, name: '暗夜潜行者 Shadow Pro', status: '定制中', date: '08-22', price: 1899, colorHex: '#26262E' },
{ id: 3, name: '熔岩橙暴 Blaze High', status: '已完成', date: '08-15', price: 1599, colorHex: '#FF6D3B' },
{ id: 4, name: '白紫交响 Symphony Low', status: '待付款', date: '08-11', price: 999, colorHex: '#EDE7FF' },
{ id: 5, name: '全息幻彩 Holo Dream', status: '已发货', date: '08-06', price: 1699, colorHex: '#D9E4FF' },
{ id: 6, name: '复古灰调 Retro Grey', status: '已取消', date: '07-28', price: 799, colorHex: '#9E9EA6' }
]
const SN_FAVORITES: SNFavoriteItem[] = [
{ id: 1, name: '电光绿锋 Bolt EZ', price: 1099, note: '等大促降价', colorHex: '#B6FF00' },
{ id: 2, name: '冰蓝极光 Aurora Mid', price: 1199, note: '夏季清爽款', colorHex: '#4FC3F7' },
{ id: 3, name: '炭黑武士 Court Dark', price: 1399, note: '实战篮球首选', colorHex: '#3A3A46' },
{ id: 4, name: '荧光夜巡 Night Patrol', price: 899, note: '夜跑安全色', colorHex: '#FFE14D' },
{ id: 5, name: '全息幻彩 Holo Dream', price: 1699, note: '联名收藏款', colorHex: '#D9E4FF' },
{ id: 6, name: '樱花粉定制版', price: 1499, note: '情侣配色款', colorHex: '#FF6EC7' }
]
const SN_SPENDS: SNSpendItem[] = [
{ id: 1, month: '3月', amount: 1299 },
{ id: 2, month: '4月', amount: 2498 },
{ id: 3, month: '5月', amount: 899 },
{ id: 4, month: '6月', amount: 3298 },
{ id: 5, month: '7月', amount: 1899 },
{ id: 6, month: '8月', amount: 2898 }
]
const SN_SIZES: number[] = [38, 39, 40, 41, 42, 43, 44]
const SN_SIZE_STOCK: number[] = [12, 18, 26, 34, 30, 22, 14]
const SN_HOME_FUNCS: SNFuncItem[] = [
{ id: 1, icon: '▲', name: '球鞋库', tab: SNTab.Shoes },
{ id: 2, icon: '◐', name: '配色', tab: SNTab.Color },
{ id: 3, icon: '✚', name: '护理', tab: SNTab.Care },
{ id: 4, icon: '⚒', name: '定制', tab: SNTab.Custom },
{ id: 5, icon: '☰', name: '订单', tab: SNTab.Mine },
{ id: 6, icon: '★', name: '会员', tab: SNTab.Mine },
{ id: 7, icon: '⚡', name: '闪购', tab: SNTab.Shoes },
{ id: 8, icon: '◉', name: '工坊', tab: SNTab.Custom }
]
const SN_CRAFT_STATS: SNCraftStat[] = [
{ id: 1, name: '喷绘', count: 156 },
{ id: 2, name: '刺绣', count: 98 },
{ id: 3, name: '激光', count: 74 },
{ id: 4, name: '编织', count: 62 },
{ id: 5, name: '中底', count: 85 },
{ id: 6, name: '热转印', count: 48 }
]
// ============ 辅助函数 ============
function snFilterShoes(cat: string): SNShoe[] {
let r: SNShoe[] = []
for (let i = 0; i < SN_SHOES.length; i++) {
if (cat === '全部' || SN_SHOES[i].cat === cat) {
r.push(SN_SHOES[i])
}
}
return r
}
function snLimitedShoes(): SNShoe[] {
let r: SNShoe[] = []
for (let i = 0; i < SN_SHOES.length; i++) {
if (SN_SHOES[i].limited) {
r.push(SN_SHOES[i])
}
}
return r
}
function snHotShoes(): SNShoe[] {
let src: SNShoe[] = []
for (let i = 0; i < SN_SHOES.length; i++) {
src.push(SN_SHOES[i])
}
src.sort((a: SNShoe, b: SNShoe) => b.heat - a.heat)
return src
}
function snTopShoes(n: number): SNShoe[] {
let r: SNShoe[] = snHotShoes()
if (r.length > n) {
r.splice(n)
}
return r
}
function snTopColors(): SNColorItem[] {
let src: SNColorItem[] = []
for (let i = 0; i < SN_COLORS_LIST.length; i++) {
src.push(SN_COLORS_LIST[i])
}
src.sort((a: SNColorItem, b: SNColorItem) => b.heat - a.heat)
return src
}
function snTopCares(): SNCareProduct[] {
let src: SNCareProduct[] = []
for (let i = 0; i < SN_CARES.length; i++) {
src.push(SN_CARES[i])
}
src.sort((a: SNCareProduct, b: SNCareProduct) => b.power - a.power)
return src
}
function snTotalHeat(): number {
let t = 0
for (let i = 0; i < SN_COLORS_LIST.length; i++) {
t += SN_COLORS_LIST[i].heat
}
return t
}
function snHeatBefore(idx: number): number {
let t = 0
for (let i = 0; i < idx; i++) {
t += SN_COLORS_LIST[i].heat
}
return t
}
function snMaxSpend(): number {
let m = 0
for (let i = 0; i < SN_SPENDS.length; i++) {
if (SN_SPENDS[i].amount > m) {
m = SN_SPENDS[i].amount
}
}
return m
}
function snMaxCraft(): number {
let m = 0
for (let i = 0; i < SN_CRAFT_STATS.length; i++) {
if (SN_CRAFT_STATS[i].count > m) {
m = SN_CRAFT_STATS[i].count
}
}
return m
}
function snMaxSizeStock(): number {
let m = 0
for (let i = 0; i < SN_SIZE_STOCK.length; i++) {
if (SN_SIZE_STOCK[i] > m) {
m = SN_SIZE_STOCK[i]
}
}
return m
}
function snStatusColor(st: string): string {
if (st === '已发货' || st === '已完成') {
return SN_COLORS.accent
}
if (st === '定制中') {
return SN_COLORS.primary
}
if (st === '待付款') {
return SN_COLORS.warn
}
return SN_COLORS.textDim
}
// ============ 入口 ============
@Entry
@Component
struct SneakerLabApp {
@State activeTab: SNTab = SNTab.Home
build() {
Column() {
// 顶部头部(电商风格,无动画)
Row({ space: 14 }) {
Column() {
Text('SNEAK LAB')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
.letterSpacing(1.5)
Text('球鞋定制工坊 · CUSTOM YOUR SOLE')
.fontSize(9)
.fontColor(SN_COLORS.accent)
.margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
Stack() {
Text('◎')
.fontSize(19)
.fontColor(SN_COLORS.textMain)
Circle()
.width(7)
.height(7)
.fill(SN_COLORS.danger)
.position({ x: 13, y: -2 })
}
.width(24)
.height(24)
.alignContent(Alignment.Center)
Text('✉')
.fontSize(19)
.fontColor(SN_COLORS.textMain)
}
.width('100%')
.padding({ left: 18, right: 18, top: 14, bottom: 12 })
.backgroundColor(SN_COLORS.bg)
.border({ width: { bottom: 1 }, color: SN_COLORS.line })
// 内容区
Stack() {
if (this.activeTab === SNTab.Home) {
SNHomeTab({ onGoTab: (t: SNTab) => {
this.activeTab = t
} })
} else if (this.activeTab === SNTab.Shoes) {
SNShoeTab()
} else if (this.activeTab === SNTab.Color) {
SNColorTab()
} else if (this.activeTab === SNTab.Care) {
SNCareTab()
} else if (this.activeTab === SNTab.Custom) {
SNCustomTab()
} else {
SNMineTab()
}
}
.layoutWeight(1)
// 底部 Tab 栏
Row() {
Column({ space: 3 }) {
Text('⌂')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Home ? SN_COLORS.accent : SN_COLORS.textDim)
Text('首页')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Home ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Home
})
Column({ space: 3 }) {
Text('▲')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Shoes ? SN_COLORS.accent : SN_COLORS.textDim)
Text('球鞋')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Shoes ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Shoes
})
Column({ space: 3 }) {
Text('◐')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Color ? SN_COLORS.accent : SN_COLORS.textDim)
Text('配色')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Color ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Color
})
Column({ space: 3 }) {
Text('✚')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Care ? SN_COLORS.accent : SN_COLORS.textDim)
Text('护理')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Care ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Care
})
Column({ space: 3 }) {
Text('⚒')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Custom ? SN_COLORS.accent : SN_COLORS.textDim)
Text('客制')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Custom ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Custom
})
Column({ space: 3 }) {
Text('☺')
.fontSize(19)
.fontColor(this.activeTab === SNTab.Mine ? SN_COLORS.accent : SN_COLORS.textDim)
Text('我的')
.fontSize(10)
.fontColor(this.activeTab === SNTab.Mine ? SN_COLORS.accent : SN_COLORS.textDim)
}
.justifyContent(FlexAlign.Center)
.layoutWeight(1)
.onClick(() => {
this.activeTab = SNTab.Mine
})
}
.width('100%')
.height(58)
.padding({ bottom: 4 })
.backgroundColor(SN_COLORS.card)
.border({ width: { top: 1 }, color: SN_COLORS.line })
}
.width('100%')
.height('100%')
.backgroundColor(SN_COLORS.bg)
}
}
// ============ 首页 Tab ============
@Component
struct SNHomeTab {
@State snLaceY: number = 0
onGoTab: (t: SNTab) => void = (t: SNTab) => {
}
private laceTimer: number = -1
aboutToAppear(): void {
this.laceTimer = setInterval(() => {
this.snLaceY = (this.snLaceY + 2) % 40
}, 50)
}
aboutToDisappear(): void {
if (this.laceTimer >= 0) {
clearInterval(this.laceTimer)
}
}
build() {
Stack() {
Scroll() {
Column({ space: 14 }) {
// 1. 鞋型仪表盘(鞋带摆动动画)
Column({ space: 12 }) {
Row() {
Text('鞋型仪表盘')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
Column().layoutWeight(1)
Text('● 实时监测')
.fontSize(10)
.fontColor(SN_COLORS.accent)
}
.width('100%')
Stack() {
Column()
.width('100%')
.height(112)
.linearGradient({
angle: 135,
colors: [['#2A1E4A', 0], ['#16161A', 1]]
})
Column({ space: 9 }) {
Row({ space: 10 }) {
Column()
.width(52)
.height(5)
.borderRadius(3)
.backgroundColor('#5E3AC8')
.translate({ y: ((((this.snLaceY + 32) % 40) - 20) * 0.4) })
Column()
.width(74)
.height(5)
.borderRadius(3)
.backgroundColor(SN_COLORS.primary)
.translate({ y: ((((this.snLaceY + 24) % 40) - 20) * 0.4) })
Column()
.width(96)
.height(5)
.borderRadius(3)
.backgroundColor(SN_COLORS.accent)
.translate({ y: ((((this.snLaceY + 16) % 40) - 20) * 0.4) })
Column()
.width(74)
.height(5)
.borderRadius(3)
.backgroundColor('#9E7BFF')
.translate({ y: ((((this.snLaceY + 8) % 40) - 20) * 0.4) })
Column()
.width(52)
.height(5)
.borderRadius(3)
.backgroundColor('#5E3AC8')
.translate({ y: ((((this.snLaceY) % 40) - 20) * 0.4) })
}
.alignItems(VerticalAlign.Center)
Row({ space: 10 }) {
Column()
.width(64)
.height(5)
.borderRadius(3)
.backgroundColor('#4A2B99')
.translate({ y: ((((this.snLaceY + 28) % 40) - 20) * 0.4) })
Column()
.width(88)
.height(5)
.borderRadius(3)
.backgroundColor('#CE9BFF')
.translate({ y: ((((this.snLaceY + 20) % 40) - 20) * 0.4) })
Column()
.width(108)
.height(5)
.borderRadius(3)
.backgroundColor(SN_COLORS.accent)
.translate({ y: ((((this.snLaceY + 12) % 40) - 20) * 0.4) })
Column()
.width(88)
.height(5)
.borderRadius(3)
.backgroundColor('#4A2B99')
.translate({ y: ((((this.snLaceY + 4) % 40) - 20) * 0.4) })
}
.alignItems(VerticalAlign.Center)
}
.justifyContent(FlexAlign.Center)
Text('LACE SYSTEM')
.fontSize(8)
.letterSpacing(2)
.fontColor('#4A4A5A')
.position({ x: 12, y: 8 })
}
.width('100%')
.height(112)
.borderRadius(14)
.clip(true)
Row() {
Column({ space: 3 }) {
Text('96.2')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.accent)
Text('今日热度指数')
.fontSize(9)
.fontColor(SN_COLORS.textSub)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column()
.width(1)
.height(30)
.backgroundColor(SN_COLORS.line)
Column({ space: 3 }) {
Text('128')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.primary)
Text('定制单进行中')
.fontSize(9)
.fontColor(SN_COLORS.textSub)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
Column()
.width(1)
.height(30)
.backgroundColor(SN_COLORS.line)
Column({ space: 3 }) {
Text('6')
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
Text('本周新品')
.fontSize(9)
.fontColor(SN_COLORS.textSub)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding({ top: 10, bottom: 10 })
.backgroundColor(SN_COLORS.cardDeep)
.borderRadius(12)
}
.width('100%')
.padding(14)
.backgroundColor(SN_COLORS.card)
.borderRadius(16)
// 2. 限量发售横滑
Column({ space: 10 }) {
Row() {
Text('限量发售')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
Column().layoutWeight(1)
Text('查看全部 >')
.fontSize(10)
.fontColor(SN_COLORS.textSub)
}
.width('100%')
Scroll() {
Row({ space: 12 }) {
ForEach(snLimitedShoes(), (s: SNShoe) => {
Column({ space: 7 }) {
Stack({ alignContent: Alignment.TopStart }) {
Column()
.width('100%')
.height(82)
.borderRadius(10)
.linearGradient({
angle: 120,
colors: [[s.colorHex, 0], ['#232331', 1]]
})
Text('LIMITED')
.fontSize(7)
.fontColor(SN_COLORS.bg)
.backgroundColor(SN_COLORS.accent)
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
.borderRadius(4)
.margin(6)
}
.width('100%')
Text(s.name)
.fontSize(11)
.fontColor(SN_COLORS.textMain)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text('¥' + s.price.toString())
.fontSize(12)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.accent)
}
.width(132)
.padding(9)
.backgroundColor(SN_COLORS.card)
.borderRadius(14)
.alignItems(HorizontalAlign.Start)
}, (s: SNShoe) => s.id.toString() + '-lim')
}
.padding({ left: 2, right: 2 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
}
.width('100%')
// 3. 本周热销榜
Column({ space: 10 }) {
Row() {
Text('本周热销榜')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
Column().layoutWeight(1)
Text('TOP 5')
.fontSize(10)
.fontColor(SN_COLORS.accent)
}
.width('100%')
Column({ space: 10 }) {
ForEach(snTopShoes(5), (s: SNShoe, idx: number) => {
Row({ space: 10 }) {
Text((idx + 1).toString())
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor(idx === 0 ? SN_COLORS.warn : (idx === 1 ? SN_COLORS.textMain : SN_COLORS.textDim))
.width(26)
.textAlign(TextAlign.Center)
Column()
.width(44)
.height(44)
.borderRadius(10)
.linearGradient({
angle: 120,
colors: [[s.colorHex, 0], ['#232331', 1]]
})
Column({ space: 5 }) {
Text(s.name)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(SN_COLORS.textMain)
.maxLines(1)
Row({ space: 6 }) {
Text(s.cat)
.fontSize(9)
.fontColor(SN_COLORS.primary)
.backgroundColor('#33264D')
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
.borderRadius(4)
Text('热度 ' + s.heat.toString())
.fontSize(9)
.fontColor(SN_COLORS.textSub)
}
Stack() {
Column()
.width('100%')
.height(4)
.borderRadius(2)
.backgroundColor(SN_COLORS.line)
Column()
.width(s.heat.toString() + '%')
.height(4)
.borderRadius(2)
.linearGradient({
angle: 0,
colors: [[SN_COLORS.primary, 0], [SN_COLORS.accent, 1]]
})
}
.width('100%')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text('¥' + s.price.toString())
.fontSize(13)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.accent)
}
.width('100%')
.padding(10)
.backgroundColor(SN_COLORS.card)
.borderRadius(12)
.alignItems(VerticalAlign.Center)
}, (s: SNShoe) => s.id.toString() + '-hot')
}
.width('100%')
}
.width('100%')
// 4. 功能宫格
Column({ space: 10 }) {
Text('功能导航')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
.width('100%')
Grid() {
ForEach(SN_HOME_FUNCS, (f: SNFuncItem) => {
GridItem() {
Column({ space: 6 }) {
Text(f.icon)
.fontSize(21)
.fontColor(SN_COLORS.accent)
Text(f.name)
.fontSize(10)
.fontColor(SN_COLORS.textSub)
}
.width('100%')
.padding({ top: 13, bottom: 13 })
.justifyContent(FlexAlign.Center)
}
.backgroundColor(SN_COLORS.card)
.borderRadius(12)
.onClick(() => {
this.onGoTab(f.tab)
})
}, (f: SNFuncItem) => f.id.toString())
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('100%')
.height(178)
}
.width('100%')
}
.width('100%')
.padding({ left: 16, right: 16, top: 14, bottom: 26 })
}
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor(SN_COLORS.bg)
}
}
// ============ 球鞋 Tab ============
@Component
struct SNShoeTab {
@State shoeCat: string = '全部'
@State showEdit: boolean = false
@State editShoe: SNShoe | null = null
@State editTarget: number = 1500
@State editHeat: number = 70
build() {
Stack() {
Scroll() {
Column({ space: 14 }) {
// 分类筛选
Row({ space: 8 }) {
Text('全部')
.fontSize(11)
.fontColor(this.shoeCat === '全部' ? SN_COLORS.bg : SN_COLORS.textSub)
.backgroundColor(this.shoeCat === '全部' ? SN_COLORS.accent : SN_COLORS.card)
.padding({ left: 13, right: 13, top: 6, bottom: 6 })
.borderRadius(14)
.onClick(() => {
this.shoeCat = '全部'
})
Text('跑步')
.fontSize(11)
.fontColor(this.shoeCat === '跑步' ? SN_COLORS.bg : SN_COLORS.textSub)
.backgroundColor(this.shoeCat === '跑步' ? SN_COLORS.accent : SN_COLORS.card)
.padding({ left: 13, right: 13, top: 6, bottom: 6 })
.borderRadius(14)
.onClick(() => {
this.shoeCat = '跑步'
})
Text('篮球')
.fontSize(11)
.fontColor(this.shoeCat === '篮球' ? SN_COLORS.bg : SN_COLORS.textSub)
.backgroundColor(this.shoeCat === '篮球' ? SN_COLORS.accent : SN_COLORS.card)
.padding({ left: 13, right: 13, top: 6, bottom: 6 })
.borderRadius(14)
.onClick(() => {
this.shoeCat = '篮球'
})
Text('休闲')
.fontSize(11)
.fontColor(this.shoeCat === '休闲' ? SN_COLORS.bg : SN_COLORS.textSub)
.backgroundColor(this.shoeCat === '休闲' ? SN_COLORS.accent : SN_COLORS.card)
.padding({ left: 13, right: 13, top: 6, bottom: 6 })
.borderRadius(14)
.onClick(() => {
this.shoeCat = '休闲'
})
Text('滑板')
.fontSize(11)
.fontColor(this.shoeCat === '滑板' ? SN_COLORS.bg : SN_COLORS.textSub)
.backgroundColor(this.shoeCat === '滑板' ? SN_COLORS.accent : SN_COLORS.card)
.padding({ left: 13, right: 13, top: 6, bottom: 6 })
.borderRadius(14)
.onClick(() => {
this.shoeCat = '滑板'
})
}
.width('100%')
// 双列球鞋 Grid
Column({ space: 10 }) {
Row() {
Text('全部球鞋')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.textMain)
Column().layoutWeight(1)
Text(snFilterShoes(this.shoeCat).length.toString() + ' 款')
.fontSize(10)
.fontColor(SN_COLORS.textSub)
}
.width('100%')
Grid() {
ForEach(snFilterShoes(this.shoeCat), (s: SNShoe) => {
GridItem() {
Column({ space: 6 }) {
Stack({ alignContent: Alignment.TopEnd }) {
Column()
.width('100%')
.height(108)
.borderRadius(10)
.linearGradient({
angle: 125,
colors: [[s.colorHex, 0], ['#1B1B22', 1]]
})
Text(s.tag)
.fontSize(8)
.fontColor(SN_COLORS.bg)
.backgroundColor(SN_COLORS.warn)
.padding({ left: 5, right: 5, top: 2, bottom: 2 })
.borderRadius(4)
.margin(6)
}
.width('100%')
Text(s.name)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(SN_COLORS.textMain)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 5 }) {
Text(s.cat)
.fontSize(9)
.fontColor(SN_COLORS.primary)
Text('·')
.fontSize(9)
.fontColor(SN_COLORS.textDim)
Text('热度 ' + s.heat.toString())
.fontSize(9)
.fontColor(SN_COLORS.textSub)
}
.width('100%')
Row({ space: 6 }) {
Text('¥' + s.price.toString())
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(SN_COLORS.accent)
Text('¥' + s.oldPrice.toString())
.fontSize(10)
.fontColor(SN_COLORS.textDim)
.decoration({ type: TextDecorationType.LineThrough })
}
.width('100%')
.alignItems(VerticalAlign.Bottom)
}
}
13.6 总结:

基于HarmonyOS ArkTS API 24的开发框架为开发者提供了强大的UI构建能力。从这个应用中我们可以看到,使用ArkTS声明式UI范式,开发者可以快速构建出功能丰富、界面美观的原生应用。
展望未来,HarmonyOS的开发技术还将继续演进。我们可以期待以下几个方向的发展:
第一,更强大的状态管理。随着应用规模的扩大,组件内部状态管理将难以满足需求。更加强大和灵活的全局状态管理方案将成为开发者的迫切需求。
第二,更丰富的动画能力。ArkTS的动画系统已经相当强大,但在复杂动画、物理动画、共享元素动画等方面还有提升空间。更丰富的动画能力将使得应用的交互体验更加流畅和自然。
第三,更完善的组件库。虽然ArkUI已经提供了丰富的内置组件,但在一些特定场景(如图表、富文本、复杂列表等)仍然需要开发者自己实现。更完善的组件生态将大大提升开发效率。
第四,更好的性能优化工具。性能优化是移动端开发永恒的主题。更智能的框架优化、更强大的性能分析工具,将帮助开发者构建更加流畅的应用。
第五,跨设备能力的深化。HarmonyOS的分布式能力是其核心竞争力之一。未来,跨设备的状态同步、UI自适应、任务流转等能力将进一步增强,为用户带来更加无缝的跨设备体验。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)