
🎯学习小目标:实现如下的购物商城主页效果

1. 在src/main/ets文件中创建components文件夹并在其中创建Home.ets和HomeProduct.ets文件。

Home 组件,进行商城主页的布局与相关功能的部署。实现代码如下:
import font from '@ohos.font'
import HomeProduct from '../components/HomeProduct'
@Component
export default struct Home{
aboutToAppear(): void {
font.registerFont({
familyName: 'myFont',
familySrc: '/fonts/iconfont.ttf'
})
}
build(){
Column(){
Row(){
Image($r('app.media.logoCircle'))
.width(40)
Row(){
TextInput({placeholder:"搜索内容"})
.layoutWeight(1)
.fontSize(16)
.backgroundColor(Color.Transparent)
Text("\ue679")
.width(40)
.height('100%')
.backgroundColor('#fa2a83')
.fontFamily('myFont')
.fontSize(20)
.fontColor('#fff')
.fontWeight('bolder')
.borderRadius({topLeft:0,topRight:20,bottomLeft:0,bottomRight:20})
.textAlign(TextAlign.Center)
}
.height(35)
.padding({left:5})
.backgroundColor('#fff')
.layoutWeight(1)
.margin({left:3})
.borderRadius(20)
}
.width('100%')
.padding({top:10,left:'10%',right:"10%",bottom:10})
.backgroundColor('#0966b4')
//主体内容
List(){
ListItem(){
Swiper(){
Image($r('app.media.img01'))
Image($r('app.media.img02'))
Image($r('app.media.img03'))
Image($r('app.media.img04'))
Image($r('app.media.img05'))
Image($r('app.media.img06'))
}
.width('100%')
.aspectRatio(2)
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(
Indicator.dot()
.itemWidth(10)
.itemHeight(10)
.selectedItemWidth(20)
.selectedItemHeight(10)
.color(Color.White)
.selectedColor(Color.Red)
)
}
ListItem(){
Grid(){
GridItem(){
Column(){
Text('\ue67d')
.listItem()
Text('店铺')
.icoText()
}
}
GridItem(){
Column(){
Text('\ue632')
.listItem()
Text('陶瓷')
.icoText()
}
}
GridItem(){
Column(){
Text('\ue61f')
.listItem()
Text('二手书')
.icoText()
}
}
GridItem(){
Column(){
Text('\ue652')
.listItem()
Text('服务')
.icoText()
}
}
}
.width('100%')
.height('100%')
.rowsTemplate('1fr')
.columnsTemplate('1fr 1fr 1fr 1fr')
}
.width('100%')
.height(100)
.margin({top:5,bottom:5})
//推荐标题
ListItem(){
Row(){
Text('推荐好物')
.fontSize('100%')
.height(30)
.fontWeight('bolder')
.fontColor('#0966b4')
Text('更多⇨')
.fontSize(12)
.fontColor('#0966b4')
}
.backgroundColor('#d1d1d1')
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.padding(10)
}
ListItem(){
HomeProduct()
}
}
.layoutWeight(1)
.backgroundColor(Color.White)
}
}
}
@Extend(Text)function listItem(){
.width(60)
.height(60)
.backgroundColor('#0966b4')
.fontFamily('myFont')
.fontSize(35)
.fontColor('#fff')
.borderRadius(30)
.textAlign(TextAlign.Center)
}
@Extend(Text)function icoText(){
.fontSize(15)
.height(30)
.fontWeight('bolder')
}该组件构建了一个具有特定布局和功能的界面,包含了搜索栏、轮播图、网格布局展示分类以及推荐好物相关展示等内容。
import font from '@ohos.font';
import HomeProduct from '../components/HomeProduct'; 从 @ohos.font 导入了 font 模块,可能用于字体相关的操作,比如注册自定义字体等,从后续代码中对字体注册的使用可以印证这一点。
导入了自定义的 HomeProduct 组件,推测这个组件用于展示具体的推荐好物等相关内容,不过代码中未给出 HomeProduct 的具体实现细节。
Home 组件定义
import font from '@ohos.font';
import HomeProduct from '../components/HomeProduct';使用 @Component 装饰器将 Home 结构体标记为一个组件,意味着它可以在界面构建中被当作一个独立的 UI 单元来使用,并且按照其内部定义的 build 方法来渲染具体的界面内容。
aboutToAppear 方法
aboutToAppear(): void {
font.registerFont({
familyName: 'myFont',
familySrc: '/fonts/iconfont.ttf'
})
}这是一个生命周期相关的方法,在组件即将显示时被调用(根据名称和常见的组件生命周期逻辑推测)。
它调用了 font 模块的 registerFont 方法,目的是注册一个名为 myFont 的自定义字体,字体文件来源指定为 /fonts/iconfont.ttf,这样后续就可以在组件中使用这个自定义字体来显示特定的文本样式了。
build 方法(核心界面构建逻辑)
整个界面构建基于 Column(列布局),在这个列布局内部嵌套了多个 Row(行布局)以及其他复杂的组件,来构建出最终的页面结构。
Row(){
Image($r('app.media.logoCircle'))
.width(40)
Row(){
TextInput({placeholder:"搜索内容"})
.layoutWeight(1)
.fontSize(16)
.backgroundColor(Color.Transparent)
Text("\ue679")
.width(40)
.height('100%')
.backgroundColor('#fa2a83')
.fontFamily('myFont')
.fontSize(20)
.fontColor('#fff')
.fontWeight('bolder')
.borderRadius({topLeft:0,topRight:20,bottomLeft:0,bottomRight:20})
.textAlign(TextAlign.Center)
}
.height(35)
.padding({left:5})
.backgroundColor('#fff')
.layoutWeight(1)
.margin({left:3})
.borderRadius(20)
}
.width('100%')
.padding({top:10,left:'10%',right:"10%",bottom:10})
.backgroundColor('#0966b4')List 组件)List(){
// 轮播图相关的 ListItem
ListItem(){
Swiper(){
Image($r('app.media.img01'))
Image($r('app.media.img02'))
Image($r('app.media.img03'))
Image($r('app.media.img04'))
Image($r('app.media.img05'))
Image($r('app.media.img06'))
}
.width('100%')
.aspectRatio(2)
.loop(true)
.autoPlay(true)
.interval(3000)
.indicator(
Indicator.dot()
.itemWidth(10)
.itemHeight(10)
.selectedItemWidth(20)
.selectedItemHeight(10)
.color(Color.White)
.selectedColor(Color.Red)
)
}
// 网格布局分类展示的 ListItem
ListItem(){
Grid(){
GridItem(){
Column(){
Text('\ue67d')
.listItem()
Text('店铺')
.icoText()
}
}
// 省略其他几个 GridItem 类似结构,都是展示不同分类
}
.width('100%')
.height('100%')
.rowsTemplate('1fr')
.columnsTemplate('1fr 1fr 1fr 1fr')
}
.width('100%')
.height(100)
.margin({top:5,bottom:5})
// 推荐标题的 ListItem
ListItem(){
Row(){
Text('推荐好物')
.fontSize('100%')
.height(30)
.fontWeight('bolder')
.fontColor('#0966b4')
Text('更多⇨')
.fontSize(12)
.fontColor('#0966b4')
}
.backgroundColor('#d1d1d1')
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
.padding(10)
}
ListItem(){
HomeProduct()
}
}
.layoutWeight(1)
.backgroundColor(Color.White)
List 组件作为一个可滚动的列表容器,里面包含多个 ListItem,每个 ListItem 呈现不同的内容块。
ListItem: Swiper 组件用于实现图片轮播效果,添加了多个 Image 组件(资源通过类似 $r('app.media.imgXX') 引用)。Swiper 设置了宽度占满父容器、固定的宽高比(aspectRatio(2)),开启循环播放(loop(true))、自动播放(autoPlay(true))且设置了轮播间隔为 3000 毫秒,同时配置了轮播指示器(Indicator.dot() 相关配置),用于显示当前轮播图片的索引等信息,以小圆点形式呈现,并且区分了选中和未选中状态的样式。ListItem: Grid 组件构建一个网格布局,里面有多个 GridItem,每个 GridItem 又包含 Column 布局,用于垂直排列图标(通过自定义字体图标对应的 Text 组件且应用 listItem 扩展样式)和对应的文字说明(应用 icoText 扩展样式),展示不同的分类,比如店铺、陶瓷等。Grid 设置了宽度、高度占满父容器,以及行列模板,定义了一行四列且均匀分配空间的布局形式。ListItem: Row 布局包含两个 Text 组件,分别显示 "推荐好物"(样式上做了字体大小、加粗、颜色等设置)和 "更多⇨"(相对小一点字体且同样设置了颜色),整体 Row 设置了背景颜色、两端对齐(justifyContent(FlexAlign.SpaceBetween))以及内边距等样式,用于呈现一个推荐好物的标题栏效果,并且可以点击 "更多⇨" 可能跳转到更多推荐内容页面(具体取决于相关交互逻辑实现,代码中未体现)。最后一个 ListItem 使用了导入的 HomeProduct 组件,用于展示具体的推荐好物详细内容,不过具体呈现依赖于 HomeProduct 组件自身的实现。
@Extend(Text)function listItem(){
.width(60)
.height(60)
.backgroundColor('#0966b4')
.fontFamily('myFont')
.fontSize(35)
.fontColor('#fff')
.borderRadius(30)
.textAlign(TextAlign.Center)
}
@Extend(Text)function icoText(){
.fontSize(15)
.height(30)
.fontWeight('bolder')
}通过 @Extend(Text) 装饰器为 Text 组件扩展了两个自定义样式函数。
listItem 函数主要用于给 Text 组件设置特定的宽高、背景颜色(使用之前注册的 myFont 字体、较大字体、白色字体颜色、圆形边框以及文本居中对齐等样式,从代码使用场景来看,可能用于那些作为图标样式展示的 Text 组件)。icoText 函数则是给 Text 组件设置相对小一点的字体大小、固定高度以及加粗字体样式,用于那些配合图标展示的文字说明部分,使整体界面文字显示更规范统一且美观。HomeProduct 组件,展示商城主页中的内容。实现代码如下:
interface Data{
src:ResourceStr
txt:string
price:number
}
@Component
export default struct HomeProduct{
@State datas: Data[] = []
@State template: string = '1fr 1fr'
aboutToAppear(): void {
for(let i=1;i<=20;i++){
this.datas.push({
src:i%2==0? $r('app.media.product7'):$r('app.media.product1'),
txt:'陶瓷产品'+i,
price:15
})
}
}
@Builder
getItem(src:ResourceStr,txt:string,price:number){
Column(){
Image(src).width('100%').borderRadius(5)
Text(txt).fontSize(15).fontWeight(FontWeight.Bold).margin({top:10})
Text(){
Span('¥ ')
.fontColor(Color.Red)
.fontSize(10)
Span(price?.toFixed(2))
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
}
}.width('100%')
}
build() {
Stack(){
Column(){
WaterFlow(){
ForEach(this.datas,(item:Data)=>{
FlowItem(){
this.getItem(item.src,item.txt,item.price)
}
},(item:Data)=>JSON.stringify(item))
}.columnsTemplate(this.template)
.rowsGap(10)
.columnsGap(10)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward:NestedScrollMode.SELF_FIRST
})
}
.height('100%')
.width('100%')
}
.width('100%')
.height('100%')
.padding(5)
.alignContent(Alignment.Bottom)
}
}该组件用于展示一系列产品相关的信息,包括产品图片、名称以及价格等内容。组件内部实现了数据初始化、单个产品项的构建以及整体产品列表的布局展示等功能。
interface Data{
src:ResourceStr
txt:string
price:number
}定义了一个名为 Data 的接口,用于规范表示产品数据的结构。其中包含三个属性:
src:类型为 ResourceStr,推测是用于引用资源(可能是图片资源等)的一种特定类型,用于指定产品对应的图片资源。txt:字符串类型,用于存放产品的文字描述,比如产品名称等相关信息。price:数值类型,用于表示产品的价格信息。HomeProduct 组件定义
@Component
export default struct HomeProduct{
//...
}
使用 @Component 装饰器将 HomeProduct 结构体标记为一个组件,意味着它可作为独立的 UI 单元参与界面构建,其界面呈现由内部的 build 方法来定义,同时还有相关的状态管理和生命周期方法等。
@State datas: Data[] = []
@State template: string = '1fr 1fr'@State 装饰器用于定义组件的响应式状态变量。datas:是一个 Data 类型的数组,初始化为空数组,用于存储要展示的多个产品的数据信息,后续会在组件的生命周期方法中进行数据填充。template:是一个字符串类型的状态变量,初始值为 '1fr 1fr',从后续使用情况看,可能用于控制产品列表布局中列的模板(比如在 WaterFlow 布局里控制列的分布比例等情况)。
aboutToAppear 生命周期方法
aboutToAppear(): void {
for(let i=1;i<=20;i++){
this.datas.push({
src:i%2==0? $r('app.media.product7'):$r('app.media.product1'),
txt:'陶瓷产品'+i,
price:15
})
}
}aboutToAppear 方法通常在组件即将显示在界面上时被触发(是组件生命周期的一部分)。
1 到 20)往 datas 数组中添加模拟的产品数据。对于每个产品: src 属性根据索引 i 的奇偶性来选择不同的图片资源(通过 $r('app.media.productX') 方式引用,具体资源加载机制依赖框架实现),这里简单地实现了交替使用两种图片资源来模拟不同产品的图片。txt 属性设置为 '陶瓷产品' 加上当前的索引值,形成一个简单的产品名称描述。price 属性统一设置为 15,模拟产品价格。getItem 构建函数
@Builder
getItem(src:ResourceStr,txt:string,price:number){
Column(){
Image(src).width('100%').borderRadius(5)
Text(txt).fontSize(15).fontWeight(FontWeight.Bold).margin({top:10})
Text(){
Span('¥ ')
.fontColor(Color.Red)
.fontSize(10)
Span(price?.toFixed(2))
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
}
}.width('100%')
}使用 @Builder 装饰器,表明这是一个用于构建 UI 片段的函数,它接收产品的相关数据(图片资源、名称、价格)作为参数来构建一个产品项的 UI 结构。
Column(列布局)来组织产品项的内容: Image 组件,使用传入的 src 参数来显示产品图片,设置宽度占满父容器并且添加了圆角样式(borderRadius(5)),使其显示更美观。Text 组件,用于显示产品的名称(传入的 txt 参数),设置了字体大小为 15,加粗字体(FontWeight.Bold)以及顶部有一定的外边距,使其与图片有间隔。Text 组件,内部使用了 Span 来分别构建价格显示的两部分(货币符号和具体价格数值),货币符号部分设置了红色字体颜色、较小的字体大小,价格数值部分同样设置为红色字体颜色并且加粗字体,整体用于清晰美观地展示产品价格信息,并且整个列布局宽度占满父容器。build 方法(核心界面构建逻辑)
build() {
Stack(){
Column(){
WaterFlow(){
ForEach(this.datas,(item:Data)=>{
FlowItem(){
this.getItem(item.src,item.txt,item.price)
}
},(item:Data)=>JSON.stringify(item))
}.columnsTemplate(this.template)
.rowsGap(10)
.columnsGap(10)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward:NestedScrollMode.SELF_FIRST
})
}
.height('100%')
.width('100%')
}
.width('100%')
.height('100%')
.padding(5)
.alignContent(Alignment.Bottom)
}整个界面构建基于 Stack(层叠布局),里面包含一个 Column(列布局),用于组织产品列表等内容。
Column 内部使用了 WaterFlow(瀑布流布局)组件来展示产品列表: ForEach 循环遍历 datas 数组中的每个产品数据项(Data 类型),对于每个数据项,在 FlowItem 中调用 getItem 函数来构建对应的产品项 UI 结构,从而实现根据数据动态生成多个产品展示项的效果。同时传递了一个用于唯一标识每个数据项的函数(这里简单地将数据项转换为 JSON 字符串来作为标识)。WaterFlow 组件设置了 columnsTemplate 为 this.template,即根据前面定义的 template 状态变量来确定列的布局模板(比如列的宽度分配比例等情况),设置了行与列之间的间隔(rowsGap(10) 和 columnsGap(10)),并且配置了嵌套滚动相关的模式(nestedScroll),用于处理滚动行为,比如规定了向前滚动(scrollForward)和向后滚动(scrollBackward)时采用的滚动模式(分别是 PARENT_FIRST 和 SELF_FIRST,涉及到和父容器滚动交互等情况)。Column 设置了高度和宽度占满父容器,而最外层的 Stack 同样设置了宽度和高度占满父容器,并且添加了一定的内边距(padding(5))以及内容对齐方式为底部对齐(alignContent(Alignment.Bottom)),整体构建出产品列表展示的完整界面布局结构。Index 组件作为应用的入口组件。实现代码如下:
import font from '@ohos.font'
import Home from "../components/Home"
@Entry
@Component
struct Index {
aboutToAppear(): void {
font.registerFont({
familyName: 'myFont',
familySrc: '/fonts/iconfont.ttf'
})
}
@State selectedIndex: number = 0
@Builder
myBuilder(itemIndex: number, title: string, ico: string) {
Column() {
Text(ico)
.width(30)
.height(30)
.fontFamily('myFont')
.fontSize(30)
.textAlign(TextAlign.Center)
.fontColor(itemIndex == this.selectedIndex ?'#fa2a83' : Color.Black)
Text(title)
.fontColor(itemIndex == this.selectedIndex ? '#fa2a83' : Color.Black)
}
}
build() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Home()
}
.tabBar(this.myBuilder(0, '首页', '\ue64c'))
TabContent() {
Text("分类内容")
}
.tabBar(this.myBuilder(1, '分类', '\ue626'))
TabContent() {
Text("购物内容")
}
.tabBar(this.myBuilder(2, '购物', '\ue604'))
TabContent() {
Text("我的内容")
}
.tabBar(this.myBuilder(3, '我的', '\ue61e'))
}
.onChange((index: number) => {
this.selectedIndex = index
})
}
}定义了一个名为 Index 的组件,它作为应用的入口组件(通过 @Entry 装饰器标识),构建了一个带有底部导航栏(通过 Tabs 组件实现)的界面结构,导航栏包含多个选项卡,点击不同选项卡可切换显示不同的内容页面,同时在切换时还实现了相应的状态更新及样式变化等功能。
import font from '@ohos.font'
import Home from "../components/Home" 从 @ohos.font 导入了 font 模块,大概率用于字体相关操作,后续代码中会使用它来注册自定义字体,以满足界面中特定字体显示需求。
导入了自定义的 Home 组件,从代码结构推测,Home 组件应该是展示应用首页相关内容的一个独立组件,这里会被整合到 Tabs 所构建的多页面切换体系当中。
Index 组件定义与入口标识
@Entry
@Component
struct Index {
//...
}
使用 @Entry 装饰器将 Index 结构体标记为整个应用的入口组件,意味着应用启动时会首先渲染这个组件所定义的界面内容。同时, @Component 装饰器表明它是一个符合组件规范的 UI 单元,其界面呈现由内部的 build 方法来确定。
aboutToAppear 生命周期方法
aboutToAppear(): void {
font.registerFont({
familyName: 'myFont',
familySrc: '/fonts/iconfont.ttf'
})
} 这是组件生命周期中在即将显示时触发的方法。在这里调用了 font 模块的 registerFont 方法,注册了一个名为 myFont 的自定义字体,字体文件来源指定为 /fonts/iconfont.ttf。注册这个字体后,后续就可以在界面中使用该字体来展示特定的文本样式了,例如显示一些自定义的图标字体等内容。
@State selectedIndex: number = 0
通过 @State 装饰器定义了一个名为 selectedIndex 的响应式状态变量,其初始值设置为 0。这个变量用于记录当前选中的选项卡索引,在后续选项卡切换以及相应 UI 样式更新时会起到关键作用,因为界面上需要根据当前选中的选项卡来展示不同的样式效果,比如改变图标和文字的颜色等。
myBuilder 构建函数
@Builder
myBuilder(itemIndex: number, title: string, ico: string) {
Column() {
Text(ico)
.width(30)
.height(30)
.fontFamily('myFont')
.fontSize(30)
.textAlign(TextAlign.Center)
.fontColor(itemIndex == this.selectedIndex?'#fa2a83' : Color.Black)
Text(title)
.fontColor(itemIndex == this.selectedIndex? '#fa2a83' : Color.Black)
}
} 使用 @Builder 装饰器表明这是一个用于构建 UI 片段的函数。
该函数接收三个参数:
itemIndex(表示当前选项卡的索引)title(选项卡对应的标题文本)ico(用于显示的图标对应的字符编码,通常结合自定义字体来显示图标样式),并基于这些参数构建一个包含图标和标题文本的 Column(列布局)UI 结构。 对于图标对应的 Text 组件:
设置了固定的宽度和高度(width(30) 和 height(30)),指定使用之前注册的 myFont 字体,字体大小为 30,文本居中对齐(textAlign(TextAlign.Center)),并且关键的是,根据当前选项卡索引(itemIndex)与记录选中索引的 selectedIndex 是否相等,来动态设置字体颜色,如果相等则显示为 #fa2a83 颜色(可能是一种突出显示的颜色,用于标识选中状态),否则显示为黑色(普通未选中状态的颜色)。
对于标题文本对应的 Text 组件,同样根据索引是否相等来动态设置字体颜色,以实现选中和未选中状态下文字颜色的不同显示效果,整体通过这个函数构建出每个选项卡对应的底部导航栏子项的展示样式。
build 方法(核心界面构建逻辑)
build() {
Tabs({ barPosition: BarPosition.End }) {
TabContent() {
Home()
}
.tabBar(this.myBuilder(0, '首页', '\ue64c'))
TabContent() {
Text("分类内容")
}
.tabBar(this.myBuilder(1, '分类', '\ue626'))
TabContent() {
Text("购物内容")
}
.tabBar(this.myBuilder(2, '购物', '\ue604'))
TabContent() {
Text("我的内容")
}
.tabBar(this.myBuilder(3, '我的', '\ue61e'))
}
.onChange((index: number) => {
this.selectedIndex = index
})
} 整个界面构建基于 Tabs 组件,用于创建多选项卡切换的布局效果,并且通过 { barPosition: BarPosition.End } 参数设置选项卡栏的位置为底部(BarPosition.End 表示底部位置,还有其他可能的位置选项如顶部等)。
在 Tabs 组件内部,有多个 TabContent 子组件,每个 TabContent 对应一个选项卡的内容页面:
TabContent 中放置了之前导入的 Home 组件,作为应用的首页内容展示,并且通过 .tabBar(this.myBuilder(0, '首页', '\ue64c')) 调用 myBuilder 函数来构建对应的底部导航栏子项样式,传入索引 0、标题 '首页' 以及对应的图标字符编码 '\ue64c',用于显示首页对应的图标和文字样式,并且能根据选中状态改变颜色。TabContent 结构类似,分别展示简单的文本内容(如 '分类内容'、'购物内容'、'我的内容' 等),同样通过调用 myBuilder 函数传入不同的参数来构建各自对应的底部导航栏子项样式,每个选项卡都有自己对应的图标和文字,以及相应的选中 / 未选中状态样式变化。.onChange((index: number) => { this.selectedIndex = index }) 为 Tabs 组件注册了一个选项卡切换的回调函数,当用户点击切换选项卡时,会触发这个回调,将当前选中的选项卡索引更新到 selectedIndex 这个状态变量中,这样就能实时根据选中情况更新界面上相关元素(如底部导航栏图标和文字颜色)的样式了,保证 UI 展示与用户操作的一致性。本次实验成功构建了具有首页及底部导航栏多页面切换功能的应用界面。首页包含搜索栏、轮播图、分类网格与推荐好物列表等丰富内容,底部导航栏切换流畅且能实现选中状态样式更新。在技术层面,深入理解并运用组件化开发提升代码可维护性与复用性,像 Home 和 HomeProduct 组件各司其职;通过 @ohos.font 模块注册自定义字体用于图标展示,增强界面特色;灵活采用多种布局组件构建复杂结构,如 Column、Row 等布局的巧妙嵌套;借助响应式状态变量与数据循环达成数据驱动 UI,确保数据与界面显示一致。实验中遇到字体资源加载、布局适配及数据与 UI 同步等问题,均通过仔细检查路径、优化布局属性设置及遵循响应式编程最佳实践得以解决。此次实验收获颇丰,不仅熟练掌握组件化、布局构建与数据驱动等关键技术,还提升了问题解决能力,为后续应用开发积累了宝贵经验。