一、介绍

在电商社交应用核心逻辑(登录、首页展示、个人中心)的基础上,跨越 11 个 API 版本(API 9 -> API 20),完成了从代码到底层的全量适配。同时,针对 OpenHarmony 6.0 的设计规范,我们对 UI 进行了现代化视觉重构,引入了卡片式设计、圆角风格及高亮的视觉层次,使其在 润和 DAYU200 开发板上呈现出焕然一新的体验。

本示例主要包含:“登录”、“首页”、“我的”三个页面,效果图如下:

在这里插入图片描述

相关概念

  • Text:显示一段文本的组件。
  • Image:图片组件,支持本地图片和网络图片的渲染展示。
  • TextInput:可以输入单行文本并支持响应输入事件的组件。
  • Button:按钮组件,可快速创建不同样式的按钮。
  • LoadingProgress:用于显示加载动效的组件。
  • Flex:应用弹性方式布局子组件的容器组件。
  • Column:沿垂直方向布局的容器。
  • Row:沿水平方向布局容器。
  • List:列表包含一系列相同宽度的列表项。适合连续、多行呈现同类数据,例如图片和文本。
  • Swiper:滑动容器,提供切换子组件显示的能力。
  • Grid:网格容器,由“行”和“列”分割的单元格所组成,通过指定“项目”所在的单元格做出各种各样的布局。

二、核心升级亮点

架构升级与版本适配

本项目严格遵循 OpenHarmony 6.0 开发规范,完成了从旧版本到 API 20 的全量迁移。通过 DevEco Studio 6.0 的重构能力与手动适配,实现了底层依赖的全面更新。

  1. 工程环境配置升级

开发环境升级:迁移至 DevEco Studio 6.0 Release,确保构建工具链支持最新的 ArkTS 特性。

编译配置更新:修改 build-profile.json5 配置文件,将应用编译版本提升至 API 20,以解锁 OpenHarmony 6.0 的完整能力。

 compileSdkVersion: 20

 compatibleSdkVersion: 20

 runtimeOS: OpenHarmony
  1. 模块化接口迁移 (Kit Migration)

针对 OpenHarmony 6.0 推出的系统能力模块化(System Capability Kits),对项目中的核心引用库进行了标准化的替换,摒弃了旧版 @ohos.* 的导入方式,全面拥抱 @kit.* 规范:

在这里插入图片描述

  1. 核心代码重构
    EntryAbility 适配:修正了 EntryAbility.ts 中的继承逻辑与模块导入,解决了因接口变更导致的真机烧录启动失败问题。

路由逻辑更新:在 LayoutAlignIndex.ets (主页) 与 Second.ets (详情页) 中,将页面跳转逻辑统一迁移至 ArkUI Kit 下的 Router 接口,确保了页面栈管理的稳定性。
严格模式适配:修复了升级 SDK 后产生的类型检查警告,确保代码符合 ArkTS 的静态类型检查规范。

视觉体验重构 (UI/UX Refactor)

摒弃了原版较为生硬的直角与灰色背景,全面拥抱卡片化 (Card-based) 设计语言:
圆角美学:针对 Banner 轮播图、商品网格、列表项,统一添加了 borderRadius 圆角处理,界面更加圆润柔和。

层次分明:将核心内容区域(如排行榜、新品首发)的背景重构为 纯白 (#FFFFFF),与浅灰底色形成鲜明对比,通过 padding 和 margin 的微调,营造悬浮感。

沉浸式布局:优化了 Swiper 与 Grid 的间距算法,使图片展示更具张力。

效果如下所示:

在这里插入图片描述

三、环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

环境搭建

本项目基于 OpenHarmony API 20 (OpenHarmony 6.0/6.1) 开发,为确保项目能够正常编译与运行,请严格按照以下步骤配置开发环境。

  1. 软硬件要求 (Requirements)

在这里插入图片描述

  1. 开发板镜像烧录 (Device Flashing)
    由于本项目依赖 API 20 的系统能力 (SysCaps),必须将 DAYU200 开发板系统升级至最新版本,否则会导致启动时报 00401004 错误。

    获取镜像:从 CI 门户下载适配 DAYU200 的 OpenHarmony 6.1 Release (或 6.0) 镜像包。
    进入烧录模式:

    使用 USB 线连接电脑与开发板。
    
    按住开发板上的 VOL-/RECOVERY 键不放。
    
    按一下 RESET 键,等待约 2 秒后松开 RECOVERY 键。
    
    此时 RKDevTool 工具应显示“发现一个 LOADER 设备”。
    

    执行烧录:导入镜像配置,点击“执行”完成系统更新。

  2. 工程导入与配置 (Project Configuration)

    打开项目:使用 DevEco Studio 打开本工程根目录。

    自动迁移 (Migrate):如果 IDE 提示迁移,请按照向导完成 Gradle/Ohpm 的自动升级。

    配置文件修改:

为了适配 API 20,我们对 build-profile.json5 进行了关键修改。请确保您的配置如下:

// entry/build-profile.json5
{
  "apiType": "stageMode",
  "buildOption": {
  },
  "targets": [
    {
      "name": "default",
      "runtimeOS": "OpenHarmony"
    }
  ]
}
code
Json5
// 根目录 build-profile.json5 (关键修改)
{
  "app": {
    "signingConfigs": [],
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compileSdkVersion": 20,   // 修改点:升级至 20
        "compatibleSdkVersion": 20, // 修改点:升级至 20
        "runtimeOS": "OpenHarmony",
        "buildOption": {
          "strictMode": {
            "useParsetsconfig": true
          }
        }
      }
    ]
  }
}

同步与签名:

点击 IDE 顶部的 Sync Now 等待依赖下载完成。
进入 Project Structure > Signing Configs,勾选 Automatically generate signature 为应用配置自动签名。

四、代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在附件下载和gitcode源码中提供下载方式。

├──entry/src/main/ets
│  ├──common
│  │  └──constants
│  │     └──CommonConstants.ets    // 常量定义
│  ├──entryability
│  │  └──EntryAbility.ts           // 入口 Ability
│  ├──pages
│  │  ├──LoginPage.ets             // 登录页
│  │  └──MainPage.ets              // 主页面(Tabs 容器)
│  ├──view
│  │  ├──Home.ets                  // [核心修改] 首页 UI 重构逻辑
│  │  └──Setting.ets               // [核心修改] 设置页 UI 重构逻辑
│  └──viewmodel
│     ├──ItemData.ets              // 数据模型
│     └──MainViewModel.ets         // 数据源(图片资源配置)
└──entry/src/main/resources        // 资源文件

实现“登录”页面

本节主要介绍“登录”页面的实现,效果图如下:

在这里插入图片描述

界面使用Column容器组件布局,由Image、Text、TextInput、Button、LoadingProgress等基础组件构成,主要代码如下:

// LoginPage.ets
@Entry
@Component
struct LoginPage {
  ...
  build() {
    Column() {
      Image($r('app.media.logo'))
       ...
      Text($r('app.string.login_page'))
        ...
      Text($r('app.string.login_more'))
        ...

      TextInput({ placeholder: $r('app.string.account') })
        ...

      TextInput({ placeholder: $r('app.string.password') })
        ...

      Row() {
        Text($r('app.string.message_login')).blueTextStyle()
        Text($r('app.string.forgot_password')).blueTextStyle()
      }
      ....

      Button($r('app.string.login'), { type: ButtonType.Capsule })
        ....
      Text($r('app.string.register_account'))
        ....

      if (this.isShowProgress) {
        LoadingProgress()
          ....
      }

      Blank()
      Text($r('app.string.other_login_method'))
        ....
      Row({ space: CommonConstants.LOGIN_METHODS_SPACE }) {
        this.imageButton($r('app.media.login_method1'))
        this.imageButton($r('app.media.login_method2'))
        this.imageButton($r('app.media.login_method3'))
      }
    }
    ....
  }
}

获取用户输入

当用户登录前,需要获取用户输入的帐号和密码才能执行登录逻辑。给TextInput设置onChange事件,在onChange事件里面实时获取用户输入的文本信息。

// LoginPage.ets
TextInput({ placeholder: $r('app.string.account') })
  .maxLength(CommonConstants.INPUT_ACCOUNT_LENGTH)
  .type(InputType.Number)
  .inputStyle()
  .onChange((value: string) => {
    this.account = value;
  })

控制LoadingProgress显示和隐藏

给登录按钮绑定onClick事件,调用login方法模拟登录。定义变量isShowProgress结合条件渲染if用来控制LoadingProgress的显示和隐藏。当用户点击按钮时设置isShowProgress为true,即显示LoadingProgress;使用定时器setTimeout设置isShowProgress 2秒后为false,即隐藏LoadingProgress,然后执行跳转到首页的逻辑。

// LoginPage.ets
@Entry
@Component
struct LoginPage {
  @State account: string = '';
  @State password: string = '';
  @State isShowProgress: boolean = false;
  private timeOutId = null;

  ...

  login() {
    if (this.account === '' || this.password === '') {
      prompt.showToast({
        message: $r('app.string.input_empty_tips')
      })
    } else {
      this.isShowProgress = true;
      if (this.timeOutId === null) {
        this.timeOutId = setTimeout(() => {
          this.isShowProgress = false;
          this.timeOutId = null;
          router.replaceUrl({ url: 'pages/MainPage' });
        }, CommonConstants.LOGIN_DELAY_TIME);
      }
    }
  }

  ...

  build() {
    Column() {
      ...

      Button($r('app.string.login'), { type: ButtonType.Capsule })
        ....
        .onClick(() => {
          this.login();
        })
      ...

      if (this.isShowProgress) {
        LoadingProgress()
          .color($r('app.color.loading_color'))
          .width($r('app.float.login_progress_size'))
          .height($r('app.float.login_progress_size'))
          .margin({ top: $r('app.float.login_progress_margin_top') })
      }

      ...
    }
    ...
  }
}

实现页面跳转

页面间的跳转可以使用router模块相关API来实现,使用前需要先导入该模块,然后使用router.replace()方法实现页面跳转。

// LoginPage.ets
import CommonConstants from '../common/constants/CommonConstants';
import { prompt } from '@kit.ArkUI';
import { router } from '@kit.ArkUI';

login() {
  if (this.account === '' || this.password === '') {
    ...
  } else {
    this.isShowProgress = true;
    if (this.timeOutId === -1) {
      this.timeOutId = setTimeout(() => {
        this.isShowProgress = false;
        this.timeOutId = -1;
        router.replaceUrl({ url: 'pages/MainPage' });
      }, CommonConstants.LOGIN_DELAY_TIME);
    }
  }
}

实现“首页”和“我的”页面

定义资源数据

由于“首页”和“我的”页面中有多处图片和文字的组合,因此提取出ItemData类。在MainViewModel.ets文件中对页面使用的资源进行定义,在MainViewModel.ets文件中定义数据。

// ItemData.ets
export default class PageResource {
  title: Resource;
  img?: Resource;
  others?: Resource;
  constructor(title: Resource, img?: Resource, others?: Resource) {
    this.title = title;
    this.img = img;
    this.others = others;
  }
}

// MainViewModel.ets
import ItemData from './temData';
export class MainViewModel {
  ...
  getFirstGridData(): Array<ItemData> {
    let firstGridData: ItemData[] = [
      new ItemData($r('app.string.my_love'), $r('app.media.love')),
      new ItemData($r('app.string.history_record'), $r('app.media.record')),
      ...
    ];
    return firstGridData;
  }
  ...
}
export default new MainViewModel();

实现页面框架

从前面介绍章节的示意图可以看出,本示例由两个tab页组成,使用Tabs组件来实现,提取tabBar的公共样式,同时设置TabContent和Tabs的backgroundColor来实现底部tabBar栏背景色突出的效果。

// MainPage.ets
Tabs({
  barPosition: BarPosition.End,
  controller: this.tabsController
}) {
  TabContent() {
    ...
  }
  ...
  .backgroundColor($r('app.color.mainPage_backgroundColor')) // “首页”的页面背景色
  .tabBar(this.TabBuilder(CommonConstants.HOME_TITLE, CommonConstants.HOME_TAB_INDEX,
  $r('app.media.home_selected'), $r('app.media.home_normal')))
  ...
}
...
.backgroundColor(Color.White)  // 底部tabBar栏背景色
...
.onChange((index: number) => {
  this.currentIndex = index;
})
...

实现“首页”内容

“首页”效果如下所示:

在这里插入图片描述

从上面效果如可以看出“首页”由三部分内容组成分别是轮播图、2*4栅格图、4*4栅格图。首先使用Swiper组件实现轮播图,无需设置图片大小。

// Home.ets
Swiper(this.swiperController) {
  ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {
    Image(img).borderRadius($r('app.float.home_swiper_borderRadius'))
  }, (img: Resource) => JSON.stringify(img.id))
}
...
.autoPlay(true)
...

然后使用Grid组件实现2*4栅格图,代码如下

// Home.ets
Grid() {
  ForEach(mainViewModel.getFirstGridData(), (item: ItemData) => {
    GridItem() {
      Column() {
        Image(item.img)
          .width($r('app.float.home_homeCell_size'))
          .height($r('app.float.home_homeCell_size'))
        Text(item.title)
          .fontSize($r('app.float.little_text_size'))
          .margin({ top: $r('app.float.home_homeCell_margin') })
      }
    }
  }, (item: ItemData) => JSON.stringify(item))
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr')
...

使用Grid组件实现4*4栅格列表栏,其中单个栅格中有一张背景图片和两行字体不同的文本,因此在Column组件中放置两个Text组件,并设置背景图,注意Grid组件必须设置高度,否则可能出现页面空白。

// Home.ets
Grid() {
  ForEach(mainViewModel.getSecondGridData(), (secondItem: ItemData) => {
    GridItem() {
      Column() {
        Text(secondItem.title)
          ...
        Text(secondItem.others)
          ...
      }
      .alignItems(HorizontalAlign.Start)
    }
    ...
    .backgroundImage(secondItem.img)
    .backgroundImageSize(ImageSize.Cover) 
    ...
  }, (secondItem: ItemData) => JSON.stringify(secondItem))
}
...
.height($r('app.float.home_secondGrid_height')) 
.columnsTemplate('1fr 1fr')
.rowsTemplate('1fr 1fr')
...

实现“我的”页内容

“我的”页面效果图如下:

在这里插入图片描述

使用List组件结合ForEach语句来实现页面列表内容,其中引用了settingCell子组件,列表间的灰色分割线可以使用Divider属性实现,代码实现如下:

// Setting.ets
List() {
  ForEach(mainViewModel.getSettingListData(), (item: ItemData) => {
    ListItem() {
      this.settingCell(item)
    }
    .height($r('app.float.setting_list_height'))
  }, (item: ItemData) => JSON.stringify(item))
}
...
.divider({  // 设置分隔线
  ...
})
...

@Builder settingCell(item: ItemData) {
  Row() {
    Row({ space: CommonConstants.COMMON_SPACE }) {
      Image(item.img)
        ...
      Text(item.title)
        ...
    }
    if (item.others === null) {
      Image($r("app.media.right_grey"))
        ...
    } else {
      Toggle({ type: ToggleType.Switch, isOn: false })
    }
  }
  .justifyContent(FlexAlign.SpaceBetween)  // 相邻元素之间距离相同
  ...
}

五、总结

本项目不仅是一次简单的代码迁移,更是一次对 OpenHarmony 6.0 (API 20) 新特性的深度探索与实践。通过本次大赛,我们成功完成了以下核心目标:

1.跨代际技术攻坚:

跨越了从 API 9 到 API 20 的多个版本差异,深入理解了 OpenHarmony 新版 Modularization (Kit化) 的架构设计。通过将 @ohos 接口全面迁移至 @kit 标准接口,并解决 SysCap 差异导致的启动异常,我们验证了高版本 SDK 在实际开发中的稳健性。

2.现代化 UI 重构:

在 ArkUI 强大的布局能力基础上,我们将原有的基础布局升级为**“圆角卡片式”**设计。通过灵活运用 BorderRadius、Padding 与 Shadow 属性,证明了 OpenHarmony 应用完全具备构建现代化、高颜值用户界面的能力。

3.全流程实战经验:

从底层的 DAYU200 镜像烧录、build-profile.json5 的工程配置适配,到上层的业务逻辑重构,我们打通了应用升级的全链路。本项目最终在真机上实现了零报错、流畅运行,为 OpenHarmony 生态中旧版本应用的迁移与升级提供了可复用的参考范本。

未来,梅科尔工作室将继续深耕 OpenHarmony 生态,探索更多创新场景与技术落地。

仓库链接:https://atomgit.com/MakerStudio/Maker_sss

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐