鸿蒙原生应用开发实战(一):项目搭建与首页概览 — 电影清单App

前言

各位开发者朋友,本系列将带领大家从零开始,使用 HarmonyOS Stage 模型和 ArkTS 语言,开发一款完整的电影清单应用。无论你是电影爱好者还是想学习鸿蒙开发,这篇文章都能帮你快速上手。

本系列共五篇,覆盖项目搭建、添加电影、列表管理、详情评价和个人中心。

本文你将学到:

  1. 项目创建与 Stage 模型
  2. 数据模型与分类设计
  3. 首页仪表盘开发
  4. 路由注册与导航
  5. 示例数据与 AppStorage

一、项目创建

1.1 新建项目

打开 DevEco Studio → Create Project → Empty Ability:

  • Project Name: MyApplication
  • Bundle Name: com.example.myapplication
  • Location: D:\harmonyos\project\6.11.12345\5\MyApplication
  • Compatible SDK: API 23 (6.1.0)

1.2 Stage 模型入口

// EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load content');
        return;
      }
    });
  }
}

二、数据模型设计

2.1 核心类型

models/MovieData.ets 中定义数据模型:

// 电影状态
export enum MovieStatus {
  WANT_TO_WATCH = 'want_to_watch',  // 想看
  WATCHING = 'watching',             // 在看
  WATCHED = 'watched'                // 已看
}

// 电影数据
export interface Movie {
  id: string;
  title: string;
  year: number;
  director: string;
  rating: number;       // 1-5 星
  status: MovieStatus;
  isFavorite: boolean;
  review: string;
  dateAdded: string;
  genreId: string;
}

// 电影分类
export interface Genre {
  id: string;
  name: string;
  icon: string;
}

2.2 预定义分类

const ALL_GENRES: Genre[] = [
  { id: 'action', name: '动作', icon: '💥' },
  { id: 'comedy', name: '喜剧', icon: '😂' },
  { id: 'drama', name: '剧情', icon: '🎭' },
  { id: 'sci_fi', name: '科幻', icon: '🚀' },
  { id: 'horror', name: '恐怖', icon: '👻' },
  { id: 'romance', name: '爱情', icon: '❤️' },
  { id: 'animation', name: '动画', icon: '🐭' },
  { id: 'thriller', name: '悬疑', icon: '🔍' },
  { id: 'documentary', name: '纪录片', icon: '📽️' },
  { id: 'fantasy', name: '奇幻', icon: '🪄' }
];

2.3 工具函数

export function getStatusLabel(status: MovieStatus): string {
  if (status === MovieStatus.WANT_TO_WATCH) return '想看';
  if (status === MovieStatus.WATCHING) return '在看';
  return '已看';
}

export function starsString(rating: number): string {
  return '★'.repeat(rating) + '☆'.repeat(5 - rating);
}

2.4 示例数据

为了首次打开页面有内容展示,我们预置了8部经典电影:

export function getSampleMovies(): Movie[] {
  return [
    { id: 'm1', title: '肖申克的救赎', year: 1994, director: '弗兰克·德拉邦特',
      rating: 5, status: MovieStatus.WATCHED, isFavorite: true,
      review: '经典中的经典', dateAdded: '2025-01-10', genreId: 'drama' },
    { id: 'm2', title: '星际穿越', year: 2014, director: '克里斯托弗·诺兰',
      rating: 5, status: MovieStatus.WATCHED, isFavorite: true,
      review: '时间、空间与爱的史诗', dateAdded: '2025-01-12', genreId: 'sci_fi' },
    // ... 更多电影
  ];
}

三、首页仪表盘开发

3.1 页面布局

┌──────────────────────────────────┐
│  🎬 电影清单              我的   │
├──────────────────────────────────┤
│  🎬 总收藏  ✅ 已看  👀 想看 ⭐ 收藏│
│   8       3       2       3     │
├──────────────────────────────────┤
│  ➕添加电影  📋全部列表  🎲随机推荐│
├──────────────────────────────────┤
│  最近添加                  全部 > │
│  ┌────────────────────────────┐  │
│  │ 🐭 疯狂动物城      ▶️ 在看  │  │
│  │ 🎭 楚门的世界      ✅ 已看  │  │
│  │ ❤️ 泰坦尼克号      👀 想看  │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘

3.2 状态管理

@Entry
@Component
struct Index {
  @State movies: Movie[] = [];
  @State totalCount: number = 0;
  @State watchedCount: number = 0;
  @State wantCount: number = 0;
  @State favoriteCount: number = 0;
  @State recentMovies: Movie[] = [];
}

3.3 数据加载与生命周期

aboutToAppear(): void {
  this.loadData();
}

onPageShow(): void {
  this.loadData();
}

loadData(): void {
  let stored = AppStorage.get<Movie[]>('movies');
  if (stored) {
    this.movies = stored;
  } else {
    let samples = getSampleMovies();
    this.movies = samples;
    AppStorage.set<Movie[]>('movies', samples);
  }
  this.calcStats();
}

3.4 @Builder 复用

ArkTS 中使用 @Builder 装饰器可以高效复用 UI 代码:

@Builder statCard(icon: string, label: string, value: string, color: string) {
  Column() {
    Text(icon).fontSize(22)
    Text(value)
      .fontSize(20).fontWeight(FontWeight.Bold)
      .fontColor(color).margin({ top: 4 })
    Text(label)
      .fontSize(12).fontColor('#999999').margin({ top: 2 })
  }
  .layoutWeight(1)
  .padding(10).backgroundColor('#FFFFFF')
  .borderRadius(12).margin({ left: 3, right: 3 })
  .alignItems(HorizontalAlign.Center)
}

使用方式:

Row() {
  this.statCard('🎬', '总收藏', this.totalCount.toString(), '#6C63FF')
  this.statCard('✅', '已看', this.watchedCount.toString(), '#2ED573')
  this.statCard('👀', '想看', this.wantCount.toString(), '#FFA502')
  this.statCard('⭐', '收藏', this.favoriteCount.toString(), '#FF6B6B')
}

3.5 路由跳转

// 页面跳转
router.pushUrl({ url: 'pages/AddMovie' });
router.pushUrl({ url: 'pages/DetailPage', params: { movieId: 'm1' } });

// 页面返回
router.back();

四、路由注册

4.1 main_pages.json

{
  "src": [
    "pages/Index",
    "pages/AddMovie",
    "pages/ListPage",
    "pages/DetailPage",
    "pages/ProfilePage"
  ]
}

五、ArkTS 严格模式指南

API 23 开启严格模式,以下是关键注意事项:

  1. @Builder 中不能有 let 声明 → 所有计算需内联或通过 @State 提前计算
  2. 不能使用解构赋值let [a, b] = arr 不允许,换成 let a = arr[0]; let b = arr[1]
  3. 对象字面量需要显式类型 → 不能写 { name: 'test' },要定义 interface 后赋值
  4. .bind() 不支持 → 使用闭包或 builder + onAction 替代
// ❌ 不支持的写法
@Builder card(item: Type) {
  let name = item.name;  // 不允许
  Row() { ... }
}

// ✅ 正确的写法
@Builder card(item: Type) {
  Row() {
    Text(item.name) ...  // 直接内联
  }
}

六、运行效果

完成开发后,首页会展示:

  • 4个统计卡片(总收藏/已看/想看/收藏)
  • 3个快捷操作按钮(添加/列表/随机推荐)
  • 最近添加的5部电影列表
  • Emoji 图标 + 电影分类标签
  • 状态标签以不同颜色区分(想看=橙/在看=绿/已看=紫)

总结

本文完成了:

  • ✅ 项目创建与 Stage 模型理解
  • ✅ 数据模型设计(Movie/Genre)
  • ✅ 首页仪表盘开发(统计卡片 + 快捷操作 + 最近列表)
  • ✅ 路由注册与页面导航
  • ✅ @Builder 组件复用

下一篇,我们将开发添加电影页面,实现表单输入、分类选择和状态切换!
在这里插入图片描述


系列目录

  • ✅ 第一篇:项目搭建与首页概览(本篇)
  • 📝 第二篇:添加电影与表单交互
  • 📝 第三篇:电影列表与搜索筛选
  • 📝 第四篇:电影详情与评分评价
  • 📝 第五篇:个人中心与数据统计
Logo

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

更多推荐