【江鸟中原】OpenHarmony 新手实战:从零开发 “温馨书屋” 图书商城应用

开篇:鸿蒙开发入门 —— 为什么选择 OpenHarmony?

在移动端开发领域,OpenHarmony(开放鸿蒙)是近年来备受关注的开源全场景分布式操作系统—— 它不仅支持手机、平板等终端,还能覆盖智能穿戴、智能家居等设备,具备 “一次开发、多端部署” 的分布式能力,是国产操作系统生态的核心组成部分。

对于新手开发者来说,OpenHarmony 的友好性体现在:

  1. 开发语言易上手:采用ArkTS(基于 TypeScript 扩展),语法接近前端常用的 JavaScript/TypeScript,有前端基础的开发者可快速入门;
  2. 工具链集成度高:官方开发工具Deveco Studio内置 SDK、模拟器、调试工具,无需额外配置复杂环境;
  3. 组件化 + 声明式 UI:提供丰富的基础组件(Text/Button/List/Swiper等),通过声明式语法即可快速搭建界面,减少冗余代码。

而 “图书商城” 类应用是 OpenHarmony 新手的理想入门项目:它需求明确、功能模块化,能覆盖页面跳转、表单交互、列表渲染、资源管理等核心开发技能,同时贴近实际应用场景,能直观感受到开发成果。

一、前言:为什么选择这个项目入门鸿蒙开发?

随着 OpenHarmony(鸿蒙)生态的快速发展,基于 ArkTS 的应用开发已成为移动端开发的热门方向。对于零基础新手来说,“图书商城” 类应用是绝佳的入门项目 —— 它覆盖了页面跳转、表单交互、列表渲染、资源管理等鸿蒙开发核心知识点,且功能模块化、逻辑简单,能快速建立开发信心。

本文将以 “温馨书屋” 为例,从「环境搭建→项目初始化→资源配置→核心页面开发→调试运行→功能扩展」全流程拆解,手把手教你开发一个包含启动页、登录 / 注册、首页、购物车、个人中心、隐私政策弹窗的完整鸿蒙应用(效果与你提供的截图完全匹配)。

适合人群:OpenHarmony 零基础开发者、ArkTS 入门学习者、想快速落地实战项目的新手;技术栈:Deveco Studio 6.0.0.848、ArkTS(API Version 9)、Node.js 18.20.1、OpenHarmony SDK;最终效果:可在鸿蒙模拟器 / 真机运行的完整应用,界面贴合你提供的截图,包含所有核心交互逻辑。

这个项目能学到什么?

如果你是 OpenHarmony 零基础开发者,想从 0 到 1 做一个能跑通的图书商城类应用,这篇教程正好适合你!

我们要做的是「温馨书屋」应用,包含启动页、登录注册、首页、分类、购物车、个人中心等核心页面(效果见下文截图),全程用ArkTS+Deveco Studio实现。

学完这篇你能掌握:

  • OpenHarmony 开发环境搭建(避坑版)
  • ArkTS 基础组件(Text/Button/List/Swiper)的使用
  • 页面跳转、表单输入、弹窗等基础交互逻辑
  • 应用资源(图片、字符串)的管理与引用
  • 从 “项目初始化” 到 “模拟器运行” 的完整流程

二、环境准备:手把手配置鸿蒙开发环境(避坑版)

新手入门第一步最容易卡壳在环境配置,以下步骤严格按 “版本匹配 + 细节标注” 编写,确保一次成功。

2.1 安装 Deveco Studio(核心工具)

2.1.1 下载与安装

官网下载地址:HarmonyOS DevEco Studio 官方下载,选择「Deveco Studio 6.0.0.848」版本(兼容性最优),下载界面如图 1 所示:

  1. 双击安装包,选择安装路径(建议非 C 盘,如D:\DevEco Studio),勾选「Add launchers dir to the PATH」;
  2. 安装过程中会自动弹出「SDK Manager」,勾选「OpenHarmony SDK」→「API Version 9」→「Full SDK」,点击「Next」自动下载(约 5-10 分钟,需耐心等待);
  3. 安装完成后,勾选「Run DevEco Studio」,点击「Finish」启动工具。

2.1.2 首次启动配置

  1. 首次启动会提示「Import Settings」,选择「Do not import settings」,点击「OK」;
  2. 进入欢迎界面后,点击「Configure」→「Settings」,检查「Appearance & Behavior」→「System Settings」→「Android SDK」,确认 OpenHarmony SDK 已安装完成。

2.2 配置 Node.js(必装依赖)

2.2.1 下载与安装

  1. Node.js 官网下载:Node.js 18.20.1 下载,选择对应系统版本(Windows 选「node-v18.20.1-x64.msi」);
  2. 安装时勾选「Add to PATH」,一路下一步完成安装,安装完成后打开终端(Win+R输入cmd),输入node -v,输出v18.20.1则安装成功。

2.2.2 验证与 Deveco 关联

回到 Deveco Studio,点击「File」→「Settings」→「Node.js」,在「Node interpreter」中选择安装的 Node.js 路径(如C:\Program Files\nodejs\node.exe),点击「Apply」→「OK」。

2.3 配置 OpenHarmony 模拟器(运行应用必备)

  1. 打开 Deveco Studio,点击顶部菜单栏「Tools」→「Device Manager」;
  2. 在弹出的窗口中选择「Phone」标签,点击「Create Device」,选择设备型号(推荐「P40」),API 版本选择「9」,点击「Next」→「Finish」,创建设备界面如图 5 所示:!
  3. 选中新建的模拟器,点击「Start」启动(首次启动需加载镜像,约 3-5 分钟),启动后的模拟器界面如图 6 所示:

模拟器启动常见问题

  • 问题 1:启动失败提示 “VT-x 未开启”→ 进入电脑 BIOS,开启「Intel Virtualization Technology」(虚拟化);
  • 问题 2:模拟器黑屏→ 关闭 360、腾讯电脑管家等杀毒软件,重新启动。

三、项目初始化:创建 “温馨书屋” 项目(细节拉满)

3.1 新建项目

  1. 回到 Deveco Studio 欢迎界面,点击「Create Project」;
  2. 模板选择:左侧选「OpenHarmony」→ 右侧选「Empty Ability」(空应用模板),点击「Next」;
  3. 项目核心配置,填写完成后点击「Finish」:
  4. 3.2 项目目录深度解析(新手必看)

    初始化完成后,项目目录所示(重点标注核心目录 / 文件的作用):

    WarmBookstore/
    ├── .idea/                # IDE配置文件(无需修改)
    ├── AppScope/             # 应用全局配置(包名、版本号等)
    │   └── app.json5         # 全局应用配置(如bundleName、versionCode)
    ├── entry/                # 主模块(所有业务代码/资源都在这里)
    │   ├── src/main/ets/     # ArkTS代码根目录
    │   │   ├── EntryAbility/ # 应用入口(页面路由注册、生命周期)
    │   │   ├── pages/        # 页面目录(所有页面放在这里)
    │   │   └── app.ets       # 应用全局入口(配置主题、全局状态)
    │   ├── src/main/resources # 资源目录(图片、字符串、颜色等)
    │   │   ├── base/         # 基础资源(默认)
    │   │   │   ├── element/  # 字符串/颜色/尺寸配置
    │   │   │   ├── media/    # 图片资源(图标、启动图、商品图等)
    │   │   │   └── layout/   # 布局配置(可选)
    │   └── module.json5      # 模块配置(应用图标、标签、权限)
    ├── oh_modules/           # 项目依赖包(自动下载)
    └── hvigorfile.ts         # 构建脚本(无需修改)
    

    3.3 初始配置修改(应用名称 / 图标)

    3.3.1 修改应用名称

  5. 打开entry/src/main/resources/base/element/string.json
  6. 修改EntryAbility_label的值为 “温馨书屋”,同时补充应用描述:
  7. {
      "string": [
        {
          "name": "EntryAbility_label",
          "value": "温馨书屋"
        },
        {
          "name": "module_desc",
          "value": "温馨书屋-专注图书销售的鸿蒙应用"
        }
      ]
    }
    

3.打开entry/src/main/module.json5,确认label字段引用该资源:

    "abilities": [
      {
        "name": "EntryAbility",
        "label": "$string:EntryAbility_label", // 关联字符串资源
        "icon": "$media:app_icon", // 应用图标引用
        // 其他配置...
      }
    ]
    

    3.3.2 替换应用图标

  1. 将自定义图标(建议尺寸 108*108,命名为app_icon.png)放入entry/src/main/resources/base/media
  2. 确保module.json5icon字段为$media:app_icon,替换后模拟器中应用图标如图 9 所示:

四、核心页面开发:从启动页到个人中心(逐页实现 + 截图)

4.1 前置准备:资源文件整理

将以下图片资源放入entry/src/main/resources/base/media(命名与代码一致):

  • 启动页:splash1.pngsplash2.png(对应你的启动页截图);
  • 应用图标:app_icon.png
  • 首页轮播:banner1.png(你的 “极价书屋” 广告图);

分类图标:icon_recommend.png(推荐)、icon_postgraduate.png(考研)、icon_campus.png(校园);

  • 商品图片:book1.png(考研英语图书);

  • 个人中心:user_avatar.png(用户头像)、icon_collect.png(收藏)、icon_record.png(记录)、icon_privacy.png(隐私);

  • 购物车:icon_empty.png(空购物车)。

4.2 路由配置:注册所有页面

打开entry/src/main/ets/EntryAbility/EntryAbility.ts,在onCreate方法中注册所有页面路由:

import UIAbility from **********';
import window from **********';
import router from **********';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    // 注册页面路由(key为页面路径,value为页面文件路径)
    router.clear();
    router.registerRouter({
      url: 'pages/Splash', // 启动页
      route: '../pages/Splash'
    });
    router.registerRouter({
      url: 'pages/Login', // 登录页
      route: '../pages/Login'
    });
    router.registerRouter({
      url: 'pages/Register', // 注册页
      route: '../pages/Register'
    });
    router.registerRouter({
      url: 'pages/Home', // 首页
      route: '../pages/Home'
    });
    router.registerRouter({
      url: 'pages/Cart', // 购物车
      route: '../pages/Cart'
    });
    router.registerRouter({
      url: 'pages/Mine', // 个人中心
      route: '../pages/Mine'
    });
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // 启动应用时跳转到启动页
    windowStage.loadContent('pages/Splash', (err, data) => {
      if (err) {
        console.error('加载页面失败:', err.message);
      }
    });
  }
}

4.3 页面 1:启动页(Swiper 轮播)

pages目录下创建Splash.ets,实现启动页轮播效果(对应你的启动页截图):

// Splash.ets
import router from **********';

@Entry
@Component
struct SplashPage {
  // 轮播图片列表(对应media目录下的图片)
  private splashImages: Resource[] = [
    $r("app.media.splash1"), // 你的启动页截图对应的图片
    $r("app.media.splash2")  // 扩展启动图(可选)
  ];

  build() {
    // 全屏轮播容器
    Swiper({
      index: 0, // 默认显示第一张
      autoplay: true, // 自动轮播
      interval: 3000, // 轮播间隔3秒
      loop: false, // 不循环(轮播完一次跳转)
      indicator: true // 显示轮播指示器(小圆点)
    }) {
      // 遍历轮播图片
      ForEach(this.splashImages, (img: Resource) => {
        SwiperItem() {
          Image(img)
            .width("100%") // 宽度占满屏幕
            .height("100%") // 高度占满屏幕
            .objectFit(ImageFit.Cover); // 图片自适应填充
        }
      })
    }
    .width("100%")
    .height("100%")
    // 轮播结束后跳转到登录页
    .onFinish(() => {
      router.pushUrl({
        url: "pages/Login" // 跳转到登录页
      }).catch(err => {
        console.error('跳转失败:', err.message);
      });
    });
  }
}

运行后启动页效果如图 :

4.4 页面 2:登录页(表单交互)

创建pages/Login.ets,实现手机号 + 密码登录(对应你的登录页截图):

// Login.ets
import router from **********';

@Entry
@Component
struct LoginPage {
  // 双向绑定输入框值(@State实现状态响应)
  @State phone: string = ""; // 手机号
  @State password: string = ""; // 密码
  @State isPhoneError: boolean = false; // 手机号验证状态
  @State isPwdError: boolean = false; // 密码验证状态

  // 手机号验证函数
  private checkPhone(): boolean {
    const phoneReg = /^1[3-9]\d{9}$/; // 手机号正则
    this.isPhoneError = !phoneReg.test(this.phone);
    return !this.isPhoneError;
  }

  // 密码验证函数(简单验证:长度≥6)
  private checkPwd(): boolean {
    this.isPwdError = this.password.length < 6;
    return !this.isPwdError;
  }

  // 登录逻辑
  private handleLogin() {
    // 先验证输入
    const isPhoneValid = this.checkPhone();
    const isPwdValid = this.checkPwd();
    if (!isPhoneValid || !isPwdValid) {
      return; // 验证失败不执行登录
    }
    // 模拟登录(实际项目对接后端接口)
    console.log('登录信息:', this.phone, this.password);
    // 登录成功跳转到首页
    router.pushUrl({
      url: "pages/Home"
    }).catch(err => {
      console.error('跳转首页失败:', err.message);
    });
  }

  build() {
    // 垂直布局,居中对齐
    Column({ 
      alignItems: ItemAlign.Center, // 水平居中
      justifyContent: FlexAlign.Center, // 垂直居中
      space: 15 // 子组件间距
    }) {
      // 应用图标
      Image($r("app.media.app_icon"))
        .width(80)
        .height(80)
        .margin({ bottom: 20 });
      
      // 登录标题
      Text("账号登录")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor("#333333")
        .margin({ bottom: 10 });

      // 手机号输入框
      TextInput({ 
        placeholder: "请输入手机号", 
        placeholderFontColor: "#999999" 
      })
        .width("80%") // 宽度占屏幕80%
        .height(45) // 高度45px
        .border({ 
          width: this.isPhoneError ? 1 : 1, 
          radius: 5, 
          color: this.isPhoneError ? "#FF4444" : "#E5E5E5" 
        }) // 错误时边框变红
        .padding({ left: 15 }) // 内边距
        .onChange((val) => {
          this.phone = val;
          // 输入时实时验证
          this.checkPhone();
        });
      // 手机号错误提示
      if (this.isPhoneError) {
        Text("请输入正确的手机号")
          .fontSize(12)
          .fontColor("#FF4444")
          .width("80%")
          .textAlign(TextAlign.Left)
          .margin({ top: 5, bottom: 5 });
      }

      // 密码输入框
      TextInput({ 
        placeholder: "请输入密码", 
        placeholderFontColor: "#999999",
        type: InputType.Password // 密码类型(隐藏输入)
      })
        .width("80%")
        .height(45)
        .border({ 
          width: this.isPwdError ? 1 : 1, 
          radius: 5, 
          color: this.isPwdError ? "#FF4444" : "#E5E5E5" 
        })
        .padding({ left: 15 })
        .onChange((val) => {
          this.password = val;
          // 输入时实时验证
          this.checkPwd();
        });
      // 密码错误提示
      if (this.isPwdError) {
        Text("密码长度不能少于6位")
          .fontSize(12)
          .fontColor("#FF4444")
          .width("80%")
          .textAlign(TextAlign.Left)
          .margin({ top: 5, bottom: 5 });
      }

      // 忘记密码(右对齐)
      Text("忘记密码")
        .fontSize(14)
        .fontColor("#0066FF")
        .width("80%")
        .textAlign(TextAlign.Right)
        .margin({ top: 5, bottom: 10 })
        .onClick(() => {
          // 后续可扩展“找回密码”页面
          console.log('点击忘记密码');
        });

      // 登录按钮
      Button("登录")
        .width("80%")
        .height(45)
        .backgroundColor("#4CAF50") // 绿色按钮
        .fontSize(16)
        .fontColor("#FFFFFF")
        .borderRadius(5)
        .onClick(() => {
          this.handleLogin(); // 触发登录逻辑
        });

      // 注册入口(文字按钮)
      Text("注册")
        .fontSize(14)
        .fontColor("#0066FF")
        .margin({ top: 20 })
        .onClick(() => {
          // 跳转到注册页
          router.pushUrl({
            url: "pages/Register"
          }).catch(err => {
            console.error('跳转注册页失败:', err.message);
          });
        });
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F8F8F8"); // 页面背景色
  }
}

运行后登录页效果如图:

4.5 页面 3:注册页(扩展)

创建pages/Register.ets,逻辑与登录页类似,增加 “确认密码” 验证:

// Register.ets
import router from **********';

@Entry
@Component
struct RegisterPage {
  @State phone: string = "";
  @State password: string = "";
  @State confirmPwd: string = "";
  @State isPhoneError: boolean = false;
  @State isPwdError: boolean = false;
  @State isConfirmPwdError: boolean = false;

  // 手机号验证(同登录页)
  private checkPhone(): boolean {
    const phoneReg = /^1[3-9]\d{9}$/;
    this.isPhoneError = !phoneReg.test(this.phone);
    return !this.isPhoneError;
  }

  // 密码验证
  private checkPwd(): boolean {
    this.isPwdError = this.password.length < 6;
    return !this.isPwdError;
  }

  // 确认密码验证
  private checkConfirmPwd(): boolean {
    this.isConfirmPwdError = this.confirmPwd !== this.password;
    return !this.isConfirmPwdError;
  }

  // 注册逻辑
  private handleRegister() {
    const isPhoneValid = this.checkPhone();
    const isPwdValid = this.checkPwd();
    const isConfirmValid = this.checkConfirmPwd();
    if (!isPhoneValid || !isPwdValid || !isConfirmValid) {
      return;
    }
    // 模拟注册成功,跳转到登录页
    console.log('注册成功:', this.phone);
    router.pushUrl({
      url: "pages/Login"
    });
  }

  build() {
    Column({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center, space: 15 }) {
      Image($r("app.media.app_icon"))
        .width(80)
        .height(80)
        .margin({ bottom: 20 });
      
      Text("账号注册")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor("#333333")
        .margin({ bottom: 10 });

      // 手机号输入框(同登录页)
      TextInput({ placeholder: "请输入手机号" })
        .width("80%")
        .height(45)
        .border({ width: 1, radius: 5, color: this.isPhoneError ? "#FF4444" : "#E5E5E5" })
        .padding({ left: 15 })
        .onChange((val) => {
          this.phone = val;
          this.checkPhone();
        });
      if (this.isPhoneError) {
        Text("请输入正确的手机号")
          .fontSize(12)
          .fontColor("#FF4444")
          .width("80%")
          .textAlign(TextAlign.Left);
      }

      // 密码输入框(同登录页)
      TextInput({ placeholder: "请设置密码", type: InputType.Password })
        .width("80%")
        .height(45)
        .border({ width: 1, radius: 5, color: this.isPwdError ? "#FF4444" : "#E5E5E5" })
        .padding({ left: 15 })
        .onChange((val) => {
          this.password = val;
          this.checkPwd();
        });
      if (this.isPwdError) {
        Text("密码长度不能少于6位")
          .fontSize(12)
          .fontColor("#FF4444")
          .width("80%")
          .textAlign(TextAlign.Left);
      }

      // 确认密码输入框
      TextInput({ placeholder: "请确认密码", type: InputType.Password })
        .width("80%")
        .height(45)
        .border({ width: 1, radius: 5, color: this.isConfirmPwdError ? "#FF4444" : "#E5E5E5" })
        .padding({ left: 15 })
        .onChange((val) => {
          this.confirmPwd = val;
          this.checkConfirmPwd();
        });
      if (this.isConfirmPwdError) {
        Text("两次输入的密码不一致")
          .fontSize(12)
          .fontColor("#FF4444")
          .width("80%")
          .textAlign(TextAlign.Left);
      }

      // 注册按钮
      Button("注册")
        .width("80%")
        .height(45)
        .backgroundColor("#4CAF50")
        .fontSize(16)
        .fontColor("#FFFFFF")
        .borderRadius(5)
        .onClick(() => {
          this.handleRegister();
        });

      // 返回登录
      Text("返回登录")
        .fontSize(14)
        .fontColor("#0066FF")
        .margin({ top: 20 })
        .onClick(() => {
          router.back(); // 返回上一页(登录页)
        });
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F8F8F8");
  }
}

运行后注册页效果如图 :

4.6 页面 4:首页(轮播 + 分类 + 商品列表)

创建pages/Home.ets,实现核心首页:

// Home.ets
@Entry
@Component
struct HomePage {
  // 轮播广告数据
  private bannerList: Resource[] = [
    $r("app.media.banner1") // 你的“极价书屋”广告图
  ];

  // 分类数据
  private categoryList: { name: string, icon: Resource }[] = [
    { name: "推荐", icon: $r("app.media.icon_recommend") },
    { name: "考研", icon: $r("app.media.icon_postgraduate") },
    { name: "校园", icon: $r("app.media.icon_campus") },
    { name: "小说", icon: $r("app.media.icon_novel") }, // 扩展分类
    { name: "教辅", icon: $r("app.media.icon_teaching") } // 扩展分类
  ];

  // 商品列表数据
  private bookList: { id: number, name: string, price: string, cover: Resource }[] = [
    { id: 1, name: "新航道2026考研英语黄皮书", price: "¥129", cover: $r("app.media.book1") },
    { id: 2, name: "2026考研数学李永乐复习全书", price: "¥99", cover: $r("app.media.book2") },
    { id: 3, name: "考研政治肖秀荣1000题", price: "¥89", cover: $r("app.media.book3") },
    { id: 4, name: "校园英语四级真题详解", price: "¥49", cover: $r("app.media.book4") }
  ];

  build() {
    // 垂直布局,允许滚动(避免内容超出屏幕)
    Column({ space: 10 }) {
      // 1. 轮播广告栏
      Swiper({ autoplay: true, loop: true, indicator: true }) {
        ForEach(this.bannerList, (img: Resource) => {
          SwiperItem() {
            Image(img)
              .width("100%")
              .height(150)
              .objectFit(ImageFit.Cover);
          }
        })
      }
      .width("100%")
      .height(150);

      // 2. 分类导航栏(横向滚动)
      List({ 
        scrollDirection: Axis.Horizontal, // 横向滚动
        edgeEffect: EdgeEffect.Spring // 弹性效果
      }) {
        ForEach(this.categoryList, (item) => {
          ListItem() {
            Column({ alignItems: ItemAlign.Center, space: 5 }) {
              Image(item.icon)
                .width(40)
                .height(40);
              Text(item.name)
                .fontSize(12)
                .fontColor("#333333");
            }
            .width(60)
            .padding(10);
          }
        })
      }
      .width("100%")
      .height(80)
      .backgroundColor("#FFFFFF");

      // 3. 商品列表标题
      Text("热门图书")
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor("#333333")
        .width("100%")
        .padding({ left: 15, top: 10, bottom: 5 });

      // 4. 商品列表(垂直滚动)
      List({ 
        edgeEffect: EdgeEffect.Spring,
        divider: { strokeWidth: 0.5, color: "#F0F0F0" } // 列表分割线
      }) {
        ForEach(this.bookList, (book) => {
          ListItem() {
            // 商品项布局(图片+文字)
            Row({ space: 15, alignItems: ItemAlign.Center }) {
              // 商品图片
              Image(book.cover)
                .width(80)
                .height(100)
                .borderRadius(5)
                .objectFit(ImageFit.Cover);
              // 商品信息
              Column({ space: 8, justifyContent: FlexAlign.Center }) {
                Text(book.name)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)
                  .fontColor("#333333")
                  .maxLines(2) // 最多显示2行
                  .lineClamp(2); // 超出省略
                Text(book.price)
                  .fontSize(14)
                  .fontColor("#FF4444")
                  .fontWeight(FontWeight.Medium);
                // 加入购物车按钮(小按钮)
                Button("加入购物车")
                  .width(80)
                  .height(25)
                  .backgroundColor("#FF4444")
                  .fontSize(12)
                  .fontColor("#FFFFFF")
                  .borderRadius(3)
                  .onClick(() => {
                    console.log('加入购物车:', book.name);
                  });
              }
              .flexGrow(1); // 占满剩余空间
            }
            .width("100%")
            .padding(15)
            .backgroundColor("#FFFFFF");
          }
          .onClick(() => {
            // 点击商品项可扩展跳转到详情页
            console.log('点击商品:', book.id);
          });
        })
      }
      .width("100%")
      .flexGrow(1); // 占满剩余高度
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F8F8F8");
  }
}

运行后首页效果如图所示:

4.7 页面 5:购物车页(空状态)

创建pages/Cart.ets,实现空购物车展示(对应你的购物车截图):

// Cart.ets
@Entry
@Component
struct CartPage {
  build() {
    Column({ 
      alignItems: ItemAlign.Center, 
      justifyContent: FlexAlign.Center, 
      space: 15 
    }) {
      // 空购物车图标
      Image($r("app.media.icon_empty"))
        .width(80)
        .height(80)
        .opacity(0.6); // 透明度60%
      // 空状态提示
      Text("暂无数据")
        .fontSize(16)
        .fontColor("#999999");
      // 去首页按钮
      Button("去首页逛逛")
        .width(120)
        .height(35)
        .backgroundColor("#4CAF50")
        .fontSize(14)
        .fontColor("#FFFFFF")
        .borderRadius(3)
        .onClick(() => {
          router.pushUrl({ url: "pages/Home" });
        });
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F8F8F8");
  }
}

运行后首页效果如图所示:

4.8 页面 6:个人中心 + 隐私政策

创建pages/Mine.ets,实现个人中心布局(对应你的个人中心截图):

// Mine.ets
import router from **********';

@Entry
@Component
struct MinePage {
  @State showPrivacyDialog: boolean = false; // 隐私政策弹窗状态

  build() {
    Column() {
      // 用户信息
      Row({ alignItems: ItemAlign.Center, space: 10, padding: 15 }) {
        Image($r("app.media.user_avatar"))
          .width(50)
          .height(50)
          .borderRadius(25);
        Column({ space: 5 }) {
          Text("温馨书屋")
            .fontSize(16)
            .fontWeight(FontWeight.Medium);
          Text("**********")
            .fontSize(12)
            .color("#999");
        }
      }
      .width("100%")
      .backgroundColor(Color.White);

      // 功能列表
      [
        { name: "收藏管理", icon: $r("app.media.icon_collect") },
        { name: "记录", icon: $r("app.media.icon_record") },
        { name: "隐私协议", icon: $r("app.media.icon_privacy"), onClick: () => this.showPrivacyDialog = true },
        { name: "热卖推荐", icon: $r("app.media.icon_hot") },
        { name: "推送开关", icon: $r("app.media.icon_push") }
      ].forEach((item) => {
        Row({ alignItems: ItemAlign.Center, space: 10, padding: 15 }) {
          Image(item.icon)
            .width(20)
            .height(20);
          Text(item.name)
            .fontSize(16);
          // 推送开关(仅示例)
          if (item.name === "推送开关") {
            Toggle({ type: ToggleType.Switch })
              .flexGrow(1)
              .textAlign(TextAlign.Right);
          }
        }
        .width("100%")
        .backgroundColor(Color.White)
        .margin({ top: 1 })
        .onClick(() => {
          if (item.onClick) item.onClick();
        });
      });

      // 退出登录
      Button("退出登录")
        .width("80%")
        .height(40)
        .backgroundColor(Color.White)
        .fontColor(Color.Red)
        .margin({ top: 20 })
        .onClick(() => {
          router.pushUrl({ url: "pages/Login" });
        });

      // 隐私政策弹窗
      Dialog({
        title: "隐私政策",
        confirm: { value: "确认", action: () => this.showPrivacyDialog = false },
        cancel: { value: "取消", action: () => this.showPrivacyDialog = false }
      })
      .open(this.showPrivacyDialog)
      .content(() => {
        Text(`1.本《隐私政策》适用于温馨书屋的全部产品和服务...`)
          .fontSize(14)
          .width("100%")
          .textAlign(TextAlign.Left)
          .maxLines(5)
          .lineClamp(5);
      });
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#f5f5f5");
  }
}

运行后个人中心效果如图 :

隐私政策弹窗效果如图 :

五、调试运行:让 “温馨书屋” 跑起来

  1. 点击 Deveco Studio 顶部Run按钮(绿色三角);
  2. 选择已启动的 OpenHarmony 模拟器,等待编译完成(首次编译约 1-2 分钟);
  3. 模拟器会自动打开 “温馨书屋” 应用。

六、新手常见坑及解决

  1. 图片不显示:确保图片放在entry/src/main/resources/base/media目录,引用格式为$r("app.media.图片名")(不带后缀);
  2. 页面跳转失败:检查router.registerRouter是否注册了对应页面,url是否与页面路径一致;
  3. 应用名称不生效:确认string.jsonEntryAbility_label的配置,且module.json5label字段引用了该资源;
  4. 模拟器启动失败:开启电脑虚拟化,关闭杀毒软件后重试。

七、总结与扩展

这篇教程带你实现了 “温馨书屋” 的核心页面,涵盖了 OpenHarmony 开发的基础流程和组件使用。

扩展方向

  • 对接后端 API,实现真实的登录、商品数据请求;
  • 增加购物车添加、结算功能;
  • 优化 UI 样式,适配平板等多设备尺寸;
  • 利用 OpenHarmony 的分布式能力,实现 “手机选书、平板查看” 的跨设备体验。

如果这篇教程帮你入门了 OpenHarmony 开发,欢迎点赞 + 收藏~有问题可以在评论区交流,一起学习鸿蒙开发!

全部评论

相关推荐

05-21 18:17
西北大学 Java
moon_91:哈哈哈哈哈哈哈哈就让他不绕弯子的回复你
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务