HarmonyOS应用<节气通>开发第2篇:首页开发(上)——Tabs架构与骨架搭建

引言

首页是应用的"门面",承载着核心功能和内容导航。在"节气通"应用中,我们采用Tabs底部导航架构,将应用分为四个主要模块:首页、知识、测验、我的。

这篇文章将带你从零开始搭建首页骨架,深入理解:

  • Tabs容器的配置和使用
  • 自定义TabBar的实现技巧
  • 状态管理在页面切换中的应用
  • 组件拆分的最佳实践

通过本文,你将掌握HarmonyOS多Tab应用的核心开发模式,为后续功能开发打下坚实基础。


学习目标

完成本文后,你将能够:

  • ✅ 使用Tabs组件构建底部导航应用
  • ✅ 实现自定义TabBar,支持主题色动态切换
  • ✅ 合理拆分首页子组件,保持代码清晰
  • ✅ 处理Tab切换时的状态保持问题
  • ✅ 应用主题系统到导航栏

需求分析

功能模块设计

模块 功能描述 技术要点
Tabs容器 四个Tab页面切换 Tabs、TabContent
自定义TabBar 底部导航栏样式 @Builder、状态同步
首页内容 展示核心功能入口 组件拆分、数据加载
主题应用 导航栏主题色 @StorageLink、动态样式

设计思路

为什么要这样设计?

首页作为应用的核心入口,需要考虑:

  1. 导航清晰: 用户能快速找到需要的功能
  2. 性能优化: Tab切换流畅,状态保持合理
  3. 可扩展性: 便于后续添加新功能模块
  4. 视觉统一: 与应用整体风格一致

方案对比

方案 优点 缺点 适用场景
单页面+条件渲染 实现简单 代码臃肿,难维护 简单应用
Tabs容器 框架支持,性能好 需要处理状态保持 推荐
路由跳转 完全独立 切换慢,无动画 不适合首页

关键决策

决策1: 使用Tabs而非路由跳转

  • 原因: Tab切换频繁,需要流畅体验
  • Tabs框架优化好,支持手势滑动
  • 状态保持更灵活

决策2: 自定义TabBar而非默认样式

  • 原因: 默认样式不符合设计规范
  • 需要支持主题色动态切换
  • 可以添加更多交互效果

技术原理深度解析

Tabs组件架构原理

组件层次结构:

复制代码
Tabs
├── TabContent (首页)
│   └── HomeContent
├── TabContent (知识)
│   └── KnowledgeContent
├── TabContent (测验)
│   └── QuizContent
├── TabContent (我的)
│   └── ProfileContent
└── TabBar (底部导航)
    ├── TabBarItem (首页)
    ├── TabBarItem (知识)
    ├── TabBarItem (测验)
    └── TabBarItem (我的)

核心原理:

  • Tabs组件基于ViewPager实现,支持左右滑动切换
  • 每个TabContent是独立的视图容器
  • TabBar负责显示导航项和处理点击事件
  • 通过index属性控制当前激活的Tab

状态管理机制:

  • @StorageLink('currentIndex'): 全局状态绑定
  • onChange(): 监听Tab切换事件
  • persistent(true): 控制Tab内容是否保持状态

@Builder装饰器原理

编译期优化:

  • @Builder在编译期生成独立的UI构建函数
  • 避免运行时动态创建开销
  • 支持参数传递和条件渲染

复用机制:

typescript 复制代码
@Builder
buildTabBar(title: string, index: number) {
  // 复用逻辑
}

// 在多个地方调用
.tabBar(this.buildTabBar('首页', 0))
.tabBar(this.buildTabBar('知识', 1))

性能优势:

  • 减少代码重复
  • 提高渲染效率
  • 便于统一维护

状态同步机制

双向绑定原理:

typescript 复制代码
@StorageLink('currentIndex') currentIndex: number = 0;

工作流程:

  1. 初始渲染时,从LocalStorage读取currentIndex
  2. 用户点击TabBar,触发onChange回调
  3. 更新currentIndex,自动同步到LocalStorage
  4. 所有绑定@StorageLink的组件自动刷新UI

全局状态共享:

  • 其他页面可以通过@StorageLink读取Tab索引
  • 支持跨页面状态同步
  • 适合全局主题、用户登录状态等场景

核心实现

步骤1: 搭建Tabs容器

功能说明

创建主页面Index,使用Tabs容器组织四个Tab页面。

运行效果

图: 底部导航包含四个Tab,当前选中的Tab高亮显示

完整代码
typescript 复制代码
// pages/Index.ets

import router from '@ohos.router';

@Entry
@Component
struct Index {
  @StorageLink('currentIndex') currentIndex: number = 0;
  @StorageLink('themeColor') themeColor: string = '#4CAF50';
  
  /**
   * 构建UI
   */
  build() {
    Tabs({ index: this.currentIndex }) {
      // Tab 1: 首页
      TabContent() {
        HomeContent()
      }
      .tabBar(this.buildTabBar('首页', 0))
      .persistent(true)  // 保持状态
      
      // Tab 2: 知识
      TabContent() {
        KnowledgeContent()
      }
      .tabBar(this.buildTabBar('知识', 1))
      .persistent(true)
      
      // Tab 3: 测验
      TabContent() {
        QuizContent()
      }
      .tabBar(this.buildTabBar('测验', 2))
      .persistent(true)
      
      // Tab 4: 我的
      TabContent() {
        ProfileContent()
      }
      .tabBar(this.buildTabBar('我的', 3))
      .persistent(true)
    }
    .vertical(false)                    // 水平滑动
    .barPosition(BarPosition.End)       // 导航栏在底部
    .onChange((index: number) => {      // Tab切换回调
      this.currentIndex = index;
    })
    .width('100%')
    .height('100%')
  }
}
代码解析

1. Tabs容器配置

原理:

  • Tabs是HarmonyOS提供的多页签容器
  • 每个TabContent代表一个页面
  • 支持水平和垂直两种布局方式

关键参数:

typescript 复制代码
Tabs({ index: this.currentIndex })  // 当前选中的Tab索引
  .vertical(false)                   // false=水平, true=垂直
  .barPosition(BarPosition.End)      // End=底部, Start=顶部
  .onChange((index) => { ... })      // 切换回调

为什么设置index?:

  • 控制初始显示的Tab
  • 可以通过修改currentIndex切换Tab
  • 与@StorageLink配合实现全局状态

2. TabContent配置

typescript 复制代码
TabContent() {
  HomeContent()
}
.tabBar(this.buildTabBar('首页', 0))
.persistent(true)

参数说明:

  • .tabBar(): 自定义导航栏样式
  • .persistent(true): 保持Tab状态,切换时不销毁

为什么要设置persistent?:

  • false(默认): 切换时销毁,节省内存
  • true: 切换时隐藏,保持状态
  • 首页有滚动位置、表单输入等,需要保持

步骤2: 实现自定义TabBar

功能说明

使用@Builder构建自定义TabBar,支持图标、文字和主题色。

运行效果

图: 自定义TabBar包含图标和文字,选中状态使用主题色高亮

完整代码
typescript 复制代码
/**
 * 构建自定义TabBar
 * @param title Tab标题
 * @param index Tab索引
 */
@Builder
buildTabBar(title: string, index: number) {
  Column() {
    // 图标
    Image(this.getTabIcon(title, index))
      .width(24)
      .height(24)
      .fillColor(index === this.currentIndex ? this.themeColor : '#999999')
    
    // 文字
    Text(title)
      .fontSize(12)
      .fontColor(index === this.currentIndex ? this.themeColor : '#999999')
      .margin({ top: 4 })
  }
  .width('100%')
  .height(56)
  .justifyContent(FlexAlign.Center)
  .backgroundColor(index === this.currentIndex ? '#F5F5F5' : Color.White)
}

/**
 * 获取Tab图标
 */
getTabIcon(title: string, index: number): Resource {
  const icons: Record<string, Resource> = {
    '首页': $r('app.media.ic_home'),
    '知识': $r('app.media.ic_knowledge'),
    '测验': $r('app.media.ic_quiz'),
    '我的': $r('app.media.ic_profile')
  };
  return icons[title] || $r('app.media.ic_default');
}
代码解析

1. @Builder装饰器

原理:

  • @Builder用于定义可复用的UI构建函数
  • 可以在build()中多次调用
  • 支持参数传递,实现动态UI

为什么用@Builder?:

  • 避免重复代码
  • 便于维护和修改
  • 支持参数化配置

2. 状态同步

typescript 复制代码
@StorageLink('currentIndex') currentIndex: number = 0;

// 在buildTabBar中使用
index === this.currentIndex ? this.themeColor : '#999999'

原理:

  • @StorageLink绑定全局状态
  • currentIndex改变时,所有TabBar自动更新
  • 实现选中状态的高亮效果

3. 图标管理

typescript 复制代码
getTabIcon(title: string, index: number): Resource {
  const icons: Record<string, Resource> = {
    '首页': $r('app.media.ic_home'),
    // ...
  };
  return icons[title];
}

最佳实践:

  • 使用Record类型管理图标映射
  • 根据title动态获取图标
  • 提供默认图标作为fallback

步骤3: 拆分首页内容组件

功能说明

将首页内容拆分为独立的HomeContent组件,保持代码清晰。

完整代码
typescript 复制代码
// components/HomeContent.ets

@Component
export struct HomeContent {
  @State holidays: HolidayItem[] = [];
  @State articles: ArticleItem[] = [];
  
  /**
   * 页面加载时执行
   */
  aboutToAppear() {
    this.loadData();
  }
  
  /**
   * 加载数据
   */
  async loadData() {
    try {
      // 加载当前节气
      this.holidays = await HolidayService.getCurrentHoliday();
      
      // 加载精选文章
      this.articles = await ArticleService.getFeaturedArticles();
    } catch (error) {
      console.error('Failed to load data:', error);
    }
  }
  
  /**
   * 构建UI
   */
  build() {
    Scroll() {
      Column({ space: 16 }) {
        // 1. 当前节气卡片
        this.buildHolidayCard()
        
        // 2. 精选文章
        this.buildFeaturedArticles()
        
        // 3. 功能入口
        this.buildFeatureGrid()
      }
      .padding(16)
      .width('100%')
    }
    .width('100%')
    .height('100%')
  }
  
  /**
   * 构建节气卡片
   */
  @Builder
  buildHolidayCard() {
    HolidayCard({
      holiday: this.holidays[0]
    })
  }
  
  /**
   * 构建精选文章
   */
  @Builder
  buildFeaturedArticles() {
    Column() {
      Text('精选文章')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      
      Row() {
        ForEach(this.articles.slice(0, 3), (article: ArticleItem) => {
          ArticleCard({ article: article })
        }, (article: ArticleItem) => article.id)
      }
      .width('100%')
    }
  }
  
  /**
   * 构建功能入口
   */
  @Builder
  buildFeatureGrid() {
    Grid() {
      ForEach(FEATURE_ITEMS, (item: FeatureItem) => {
        GridItem() {
          FeatureCard({ item: item })
        }
      }, (item: FeatureItem) => item.id)
    }
    .columnsTemplate('1fr 1fr')
    .rowsGap(12)
    .columnsGap(12)
    .height(200)
  }
}
代码解析

1. 组件拆分原则

单一职责:

  • HomeContent只负责首页内容展示
  • 不包含TabBar逻辑
  • 数据加载和UI渲染分离

为什么拆分为独立组件?:

  • 提高代码可读性
  • 便于测试和维护
  • 可以在其他页面复用

2. 数据加载时机

typescript 复制代码
aboutToAppear() {
  this.loadData();
}

原理:

  • aboutToAppear在组件创建时调用
  • 此时组件已挂载,可以执行异步操作
  • 数据加载完成后,@State自动触发UI更新

3. ForEach渲染列表

typescript 复制代码
ForEach(this.articles.slice(0, 3), (article: ArticleItem) => {
  ArticleCard({ article: article })
}, (article: ArticleItem) => article.id)

参数说明:

  • 第一个参数: 数据源(数组)
  • 第二个参数: 渲染函数(返回组件)
  • 第三个参数: key生成函数(唯一标识)

为什么需要key?:

  • 框架用key追踪列表项
  • 提高渲染性能
  • 避免不必要的重新渲染

组件封装详解

TabBar组件设计

虽然TabBar使用@Builder实现,但我们可以提取设计原则:

设计目标:

  • 输入: 标题、索引、图标
  • 输出: 导航栏UI
  • 交互: 点击切换Tab

Props设计(如果封装为独立组件):

typescript 复制代码
interface TabBarItemProps {
  title: string;              // 标题(必需)
  index: number;              // 索引(必需)
  icon: Resource;             // 图标(必需)
  isActive: boolean;          // 是否选中(必需)
  themeColor: string;         // 主题色(必需)
  onClick?: () => void;       // 点击回调(可选)
}

为什么这样设计:

  • title、index、icon是TabBar的核心元素
  • isActive控制选中状态
  • themeColor支持主题切换
  • onClick提供交互能力

实际应用场景

场景1: 动态Tab配置

在实际项目中,Tab配置可能来自后端API,需要动态生成Tab。

typescript 复制代码
// pages/Index.ets
@Entry
@Component
struct Index {
  @StorageLink('currentIndex') currentIndex: number = 0;
  @State tabs: TabConfig[] = [];
  
  aboutToAppear() {
    this.loadTabConfig();
  }
  
  async loadTabConfig() {
    // 从后端获取Tab配置
    const config = await ConfigService.getTabConfig();
    this.tabs = config.tabs;
  }
  
  build() {
    Tabs({ index: this.currentIndex }) {
      ForEach(this.tabs, (tab: TabConfig) => {
        TabContent() {
          // 动态加载对应组件
          this.loadTabContent(tab.component)
        }
        .tabBar(this.buildTabBar(tab.title, tab.icon, tab.index))
        .persistent(tab.persistent)
      }, (tab) => tab.id)
    }
    .onChange((index) => {
      this.currentIndex = index;
    })
  }
  
  @Builder
  loadTabContent(componentName: string) {
    // 动态组件加载
    if (componentName === 'HomeContent') {
      HomeContent()
    } else if (componentName === 'KnowledgeContent') {
      KnowledgeContent()
    }
    // ... 其他组件
  }
}

interface TabConfig {
  id: string;
  title: string;
  icon: Resource;
  index: number;
  component: string;
  persistent: boolean;
}

场景2: 带角标的TabBar

某些场景需要在Tab上显示角标(如消息数量)。

typescript 复制代码
@Builder
buildTabBar(title: string, index: number) {
  Column() {
    Stack({ alignContent: Alignment.TopEnd }) {
      Image(this.getTabIcon(title, index))
        .width(24)
        .height(24)
        .fillColor(index === this.currentIndex ? this.themeColor : '#999999')
      
      // 角标
      if (this.getBadgeCount(title) > 0) {
        Text(this.getBadgeCount(title).toString())
          .fontSize(10)
          .fontColor('#FFFFFF')
          .backgroundColor('#FF5252')
          .borderRadius(10)
          .padding({ left: 4, right: 4, top: 1, bottom: 1 })
          .position({ right: -8, top: -4 })
      }
    }
    
    Text(title)
      .fontSize(12)
      .fontColor(index === this.currentIndex ? this.themeColor : '#999999')
      .margin({ top: 4 })
  }
  .width('100%')
  .height(56)
  .justifyContent(FlexAlign.Center)
}

// 获取角标数量
getBadgeCount(title: string): number {
  const badges: Record<string, number> = {
    '消息': 3,
    '通知': 1
  };
  return badges[title] || 0;
}

场景3: Tab切换动画定制

自定义Tab切换时的动画效果。

typescript 复制代码
Tabs({ index: this.currentIndex }) {
  // ... TabContent
}
.onChange((index: number) => {
  this.currentIndex = index;
  this.playSwitchAnimation();
})

// 自定义切换动画
playSwitchAnimation() {
  animateTo({
    duration: 300,
    curve: Curve.EaseInOut,
    iterations: 1
  }, () => {
    // 动画效果
    this.tabScale = 1.1;
  })
}

常见问题与解决方案

问题1: Tab切换时状态丢失

现象 :

从首页切换到知识页,再切回来时,首页的滚动位置重置到顶部。

原因 :

默认情况下,Tabs会销毁不可见的TabContent以节省内存。

解决方案:

typescript 复制代码
TabContent() {
  HomeContent()
}
.persistent(true)  // ✅ 保持Tab状态,不销毁

原理:

  • .persistent(false): 切换时销毁,节省内存,但状态丢失
  • .persistent(true): 切换时隐藏,保持状态,占用更多内存

建议:

  • 内容少的Tab: 可以不用persistent
  • 有滚动位置、表单输入的Tab: 必须设置persistent

问题2: 自定义TabBar不生效

现象 :

设置了.tabBar(),但仍然显示默认的Tab样式。

错误代码:

typescript 复制代码
// ❌ 错误写法
TabContent() {
  HomeContent()
}
.backgroundColor(Color.White)  // 其他修饰符
.tabBar(this.buildTabBar(...))  // tabBar在最后

正确代码:

typescript 复制代码
// ✅ 正确写法
TabContent() {
  HomeContent()
}
.tabBar(this.buildTabBar(...))  // tabBar紧跟TabContent
.backgroundColor(Color.White)   // 其他修饰符在tabBar之后

原理:

  • .tabBar()必须紧跟在TabContent()之后
  • 其他修饰符可以放在tabBar之后
  • 顺序错误会导致tabBar不生效

问题3: currentIndex不同步

现象 :

点击Tab切换,但currentIndex没有更新。

原因:

  • 未绑定@StorageLink
  • onChange回调未正确实现
  • 使用了@State而非@StorageLink

解决方案:

typescript 复制代码
// ✅ 正确: 使用@StorageLink
@StorageLink('currentIndex') currentIndex: number = 0;

Tabs({ index: this.currentIndex })
  .onChange((index: number) => {
    this.currentIndex = index;  // 更新状态
  })

原理:

  • @StorageLink绑定全局状态
  • onChange回调在Tab切换时触发
  • 更新currentIndex会同步到所有绑定的地方

问题4: ForEach key重复

现象 :

列表渲染异常,某些项不显示或显示错误。

原因 :

key生成函数返回重复的值。

错误代码:

typescript 复制代码
// ❌ 错误: 使用index作为key
ForEach(items, (item, index) => {
  ItemCard({ item })
}, (item, index) => index.toString())  // 删除后key会重复

正确代码:

typescript 复制代码
// ✅ 正确: 使用唯一ID作为key
ForEach(items, (item) => {
  ItemCard({ item })
}, (item) => item.id)  // 唯一标识

原理:

  • key用于追踪列表项
  • 删除或插入时,key不能重复
  • 使用唯一ID(如数据库主键)最安全

问题5: 主题色不生效

现象 :

修改了themeColor,但TabBar颜色没有变化。

原因:

  • 未使用@StorageLink绑定
  • 颜色值格式错误
  • UI未触发重新渲染

解决方案:

typescript 复制代码
// ✅ 正确: 绑定全局状态
@StorageLink('themeColor') themeColor: string = '#4CAF50';

// 在TabBar中使用
.fillColor(index === this.currentIndex ? this.themeColor : '#999999')

原理:

  • @StorageLink自动监听状态变化
  • themeColor改变时,所有使用的地方自动更新
  • 确保颜色值格式正确(如'#4CAF50')

问题6: 组件销毁时资源未释放

现象 :

切换Tab后,定时器、监听器等仍然在运行,导致内存泄漏。

原因 :

未在组件销毁时清理资源。

解决方案:

typescript 复制代码
@Component
export struct HomeContent {
  private timerId: number = 0;
  
  aboutToAppear() {
    this.timerId = setInterval(() => {
      // 定时任务
    }, 1000);
  }
  
  aboutToDisappear() {
    if (this.timerId) {
      clearInterval(this.timerId);  // ✅ 清理定时器
    }
  }
}

原理:

  • aboutToDisappear()在组件销毁前调用
  • 必须清理定时器、网络请求、监听器等资源
  • 避免内存泄漏和性能问题

性能优化细节

优化1: Tab预加载

预加载相邻Tab,提升切换流畅度。

typescript 复制代码
Tabs({ index: this.currentIndex })
  .cacheMode(TabCacheMode.CACHE_MODE_AHEAD)  // 预加载下一个Tab
  .cacheCount(2)  // 缓存数量

效果:

  • 首次切换到下一个Tab时无需等待
  • 内存占用略有增加
  • 适合内容较多的Tab

优化2: 懒加载非首屏内容

首页内容较多时,采用懒加载策略。

typescript 复制代码
@Component
export struct HomeContent {
  @State holidays: HolidayItem[] = [];
  @State articles: ArticleItem[] = [];
  @State features: FeatureItem[] = [];
  
  aboutToAppear() {
    // 优先加载首屏内容
    this.loadHolidays();
    // 延迟加载其他内容
    setTimeout(() => {
      this.loadArticles();
    }, 500);
    setTimeout(() => {
      this.loadFeatures();
    }, 1000);
  }
}

原理:

  • 首屏内容优先加载,提升首屏渲染速度
  • 非首屏内容延迟加载,减少初始加载压力
  • 用户滚动到相应位置时内容已准备好

优化3: 图片懒加载

typescript 复制代码
@Component
struct LazyImage {
  @Prop src: string = '';
  @State isLoaded: boolean = false;
  @State isInView: boolean = false;
  
  build() {
    Stack() {
      // 占位图
      Image($r('app.media.ic_placeholder'))
        .width('100%')
        .height(160)
        .opacity(this.isLoaded ? 0 : 1)
      
      // 实际图片
      Image(this.src)
        .width('100%')
        .height(160)
        .opacity(this.isLoaded ? 1 : 0)
        .onComplete(() => {
          this.isLoaded = true;
        })
    }
    .onAppear(() => {
      this.isInView = true;
    })
  }
}

效果:

  • 只加载可视区域内的图片
  • 减少初始网络请求
  • 提升页面加载速度

优化4: 避免不必要的重新渲染

typescript 复制代码
// ❌ 每次build都创建新对象
@Builder
buildTabBar(title: string, index: number) {
  Column() {
    Image(this.getTabIcon(title, index))
      // ...
  }
  .backgroundColor(index === this.currentIndex ? '#F5F5F5' : Color.White)
}

// ✅ 使用稳定的颜色变量
@Builder
buildTabBar(title: string, index: number) {
  const bgColor = index === this.currentIndex ? '#F5F5F5' : '#FFFFFF';
  Column() {
    Image(this.getTabIcon(title, index))
      // ...
  }
  .backgroundColor(bgColor)
}

原理:

  • 避免每次渲染都创建新的Color对象
  • 使用字符串颜色值更稳定
  • 减少不必要的UI更新

本章小结

核心知识点

本文详细讲解了HarmonyOS Tabs架构的实现,主要包括:

1. Tabs容器配置

  • index: 控制当前显示的Tab
  • vertical: 滑动方向(水平/垂直)
  • barPosition: 导航栏位置(顶部/底部)
  • onChange: Tab切换回调

2. 自定义TabBar

  • 使用@Builder构建自定义样式
  • 根据currentIndex动态设置颜色
  • .tabBar()必须紧跟TabContent

3. 状态管理

  • @StorageLink: 全局状态同步
  • @State: 组件内部状态
  • .persistent(true): 保持Tab状态

4. 组件拆分

  • 首页内容独立封装为HomeContent
  • 子模块进一步拆分为小组件
  • 提高代码复用性和可维护性

最佳实践总结

Tabs配置

typescript 复制代码
Tabs({ index: this.currentIndex })
  .vertical(false)
  .barPosition(BarPosition.End)
  .onChange((index) => { this.currentIndex = index; })

自定义TabBar

typescript 复制代码
@Builder
buildTabBar(title: string, index: number) {
  Column() {
    Image(icon).fillColor(index === this.currentIndex ? themeColor : '#999')
    Text(title).fontColor(index === this.currentIndex ? themeColor : '#999')
  }
}

状态保持

typescript 复制代码
TabContent() {
  HomeContent()
}
.persistent(true)  // 保持状态

组件拆分

typescript 复制代码
@Component
export struct HomeContent {
  // 独立的首页内容组件
}

下一步预告

下一篇文章将讲解首页开发(下),实现首页的具体功能模块:

  • 当前节气卡片展示
  • 精选文章横向滚动
  • 功能入口网格布局
  • 数据加载和状态管理

相关链接

相关推荐
程序猿追5 小时前
HarmonyOS——模拟器上写个扫雷的夜晚
华为·harmonyos
G_dou_5 小时前
Flutter三方库适配OpenHarmony【bmi_calculator】BMI 计算器项目完整实战
flutter·harmonyos
大雷神6 小时前
第41篇|补光与水印:效果选项如何参与最终照片记录
harmonyos
大雷神6 小时前
第39篇|拍摄模式切换:单拍、双拍、顺序拍的 UI 逻辑
harmonyos
yuegu7777 小时前
HarmonyOS应用<节气通>开发第10篇:测验记录与错题本
华为·harmonyos
G_dou_7 小时前
Flutter三方库适配OpenHarmony【tip_calculator】小费计算器项目完整实战
flutter·harmonyos
yuegu7777 小时前
HarmonyOS应用<节气通>开发第6篇:节气详情页(下)——诗词与养生
华为·harmonyos
慧海灵舟8 小时前
鸿蒙南向开发教程Day 2:创建自己的 Hello World 工程
华为·harmonyos·写文章,赢小鸿ai
颜淡慕潇8 小时前
鸿蒙 PC的 vcpkg 交叉编译库在x86_64宿主环境下的AI自动化验证方案
人工智能·自动化·harmonyos