引言

想用鸿蒙NEXT开发一个完整的培训班管理系统,却不知道从何下手?项目结构混乱、模块划分不清晰、代码难以维护?别担心,本文将手把手带你从零搭建一个高内聚低耦合的鸿蒙NEXT项目架构,涵盖需求分析、架构设计、目录规划、环境搭建和基础框架实现,让你的培训班管理系统开发事半功倍!


一、需求分析

1.1 业务背景

培训班管理系统是一个典型的教育类应用,主要用于管理培训机构的日常运营,包括学员管理、课程管理、报名管理、考勤管理等核心功能。

1.2 功能需求

核心功能模块

模块

功能点

优先级

学员管理

学员信息录入、查询、修改、删除

P0

课程管理

课程信息维护、课程表展示

P0

报名管理

学员报名、退费、课程选择

P0

考勤管理

签到签退、考勤统计

P1

数据统计

学员统计、课程统计、收入统计

P1

用户角色
  • 管理员:系统管理、数据统计、权限管理

  • 教师:课程管理、考勤管理、学员查看

  • 学员:课程查看、报名、个人信息管理

1.3 界面原型

┌─────────────────────────────────────┐
│           首页 (TabBar)              │
├─────────────────────────────────────┤
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐  │
│  │ 学员 │ │ 课程 │ │ 报名 │ │ 我的 │  │
│  └─────┘ └─────┘ └─────┘ └─────┘  │
└─────────────────────────────────────┘

二、架构设计

2.1 整体架构

采用分层架构设计,将应用分为以下层次:

┌─────────────────────────────────────┐
│           UI层 (ArkUI)              │
├─────────────────────────────────────┤
│         ViewModel层                 │
├─────────────────────────────────────┤
│         Service层                   │
├─────────────────────────────────────┤
│         Data层                      │
└─────────────────────────────────────┘

2.2 技术选型

技术领域

技术方案

说明

UI框架

ArkUI

鸿蒙原生UI框架

状态管理

@State/@Prop/@Link

组件状态管理

数据持久化

Preferences + RDB

轻量级+关系型数据库

网络请求

@ohos.net.http

系统HTTP API

路由管理

Navigation + NavRouter

页面导航

2.3 模块划分

TrainingManagement/
├── entry/                    # 主模块
│   ├── src/main/ets/
│   │   ├── pages/           # 页面
│   │   ├── components/      # 公共组件
│   │   ├── viewmodel/       # 视图模型
│   │   ├── service/         # 业务服务
│   │   ├── model/           # 数据模型
│   │   ├── common/          # 公共工具
│   │   └── resources/       # 资源文件
│   └── src/main/resources/  # 系统资源
└── features/                 # 功能模块
    ├── student/             # 学员模块
    ├── course/              # 课程模块
    ├── enrollment/          # 报名模块
    └── attendance/          # 考勤模块

三、目录结构规划

3.1 完整目录结构

entry/src/main/ets/
├── pages/
│   ├── Index.ets                    # 首页
│   ├── StudentList.ets              # 学员列表页
│   ├── StudentDetail.ets            # 学员详情页
│   ├── CourseList.ets               # 课程列表页
│   ├── CourseDetail.ets             # 课程详情页
│   ├── EnrollmentPage.ets           # 报名页面
│   └── MyPage.ets                   # 个人中心
├── components/
│   ├── common/
│   │   ├── TitleBar.ets             # 标题栏
│   │   ├── SearchBar.ets            # 搜索栏
│   │   ├── EmptyView.ets            # 空状态视图
│   │   └── LoadingView.ets          # 加载视图
│   ├── student/
│   │   ├── StudentCard.ets          # 学员卡片
│   │   └── StudentForm.ets          # 学员表单
│   └── course/
│       ├── CourseCard.ets           # 课程卡片
│       └── CourseSchedule.ets       # 课程表
├── viewmodel/
│   ├── StudentViewModel.ets         # 学员视图模型
│   ├── CourseViewModel.ets          # 课程视图模型
│   └── EnrollmentViewModel.ets      # 报名视图模型
├── service/
│   ├── StudentService.ets           # 学员服务
│   ├── CourseService.ets            # 课程服务
│   └── EnrollmentService.ets        # 报名服务
├── model/
│   ├── Student.ets                  # 学员模型
│   ├── Course.ets                   # 课程模型
│   └── Enrollment.ets               # 报名模型
├── common/
│   ├── Constants.ets                # 常量定义
│   ├── Utils.ets                    # 工具类
│   └── DatabaseHelper.ets           # 数据库帮助类
└── resources/
    ├── base/element/                # 元素资源
    ├── base/media/                  # 媒体资源
    └── base/profile/                # 配置文件

3.2 模块职责说明

目录

职责

说明

pages

页面层

负责UI展示和用户交互

components

组件层

可复用的UI组件

viewmodel

视图模型层

管理页面状态和业务逻辑

service

服务层

处理业务逻辑和数据操作

model

模型层

定义数据结构

common

公共层

工具类和常量定义


四、开发环境搭建

4.1 DevEco Studio安装

  1. 下载DevEco Studio

    • 访问华为开发者联盟官网

    • 下载最新版本DevEco Studio 5.0+

  2. 安装配置

    系统要求:
    - Windows 10/11 64位
    - 内存:16GB以上
    - 硬盘:100GB以上可用空间
  3. SDK配置

    • 打开DevEco Studio

    • 进入Settings → HarmonyOS SDK

    • 下载API 12及以上版本

4.2 项目创建

  1. 创建新项目

    • 打开DevEco Studio

    • 选择"Create HarmonyOS Project"

    • 选择"Empty Ability"模板

    • 配置项目信息:

      • Project Name: TrainingManagement

      • Bundle Name: com.example.training

      • Compile SDK: API 12

      • Model: Stage

  2. 项目初始化

    # 项目结构生成后,检查以下文件
    - entry/src/main/ets/entryability/EntryAbility.ets
    - entry/src/main/ets/pages/Index.ets
    - entry/src/main/module.json5

4.3 开发工具配置

  1. 代码风格配置

    // .eslintrc.json
    {
      "extends": "@typescript-eslint/recommended",
      "rules": {
        "indent": ["error", 2],
        "quotes": ["error", "single"],
        "semi": ["error", "always"]
      }
    }
  2. Git配置

    # 初始化Git仓库
    git init
    git add .
    git commit -m "初始化鸿蒙培训班管理系统项目"

五、基础框架代码实现

5.1 数据模型定义

// model/Student.ets
export interface Student {
  id: string;
  name: string;
  phone: string;
  email: string;
  gender: number; // 0: 未知, 1: 男, 2: 女
  birthday: string;
  address: string;
  avatar: string;
  createTime: string;
  updateTime: string;
}
// model/Course.ets
export interface Course {
  id: string;
  name: string;
  description: string;
  teacher: string;
  price: number;
  duration: number; // 课时
  maxStudents: number;
  currentStudents: number;
  startTime: string;
  endTime: string;
  status: number; // 0: 未开始, 1: 进行中, 2: 已结束
  createTime: string;
  updateTime: string;
}
// model/Enrollment.ets
export interface Enrollment {
  id: string;
  studentId: string;
  courseId: string;
  enrollTime: string;
  status: number; // 0: 已报名, 1: 已上课, 2: 已退费
  remark: string;
  createTime: string;
  updateTime: string;
}

5.2 数据库帮助类

// common/DatabaseHelper.ets
import rdb from '@ohos.data.relationalStore';

const DB_NAME = 'training_management.db';
const DB_VERSION = 1;

export class DatabaseHelper {
  private static instance: DatabaseHelper;
  private rdbStore: rdb.RdbStore | null = null;

  private constructor() {}

  static getInstance(): DatabaseHelper {
    if (!DatabaseHelper.instance) {
      DatabaseHelper.instance = new DatabaseHelper();
    }
    return DatabaseHelper.instance;
  }

  async init(context: Context): Promise<void> {
    const config: rdb.StoreConfig = {
      name: DB_NAME,
      securityLevel: rdb.SecurityLevel.S1
    };

    this.rdbStore = await rdb.getRdbStore(context, config, DB_VERSION);
    await this.createTables();
  }

  private async createTables(): Promise<void> {
    const createStudentTable = `
      CREATE TABLE IF NOT EXISTS student (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        phone TEXT,
        email TEXT,
        gender INTEGER DEFAULT 0,
        birthday TEXT,
        address TEXT,
        avatar TEXT,
        create_time TEXT,
        update_time TEXT
      )
    `;

    const createCourseTable = `
      CREATE TABLE IF NOT EXISTS course (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        description TEXT,
        teacher TEXT,
        price REAL,
        duration INTEGER,
        max_students INTEGER,
        current_students INTEGER DEFAULT 0,
        start_time TEXT,
        end_time TEXT,
        status INTEGER DEFAULT 0,
        create_time TEXT,
        update_time TEXT
      )
    `;

    const createEnrollmentTable = `
      CREATE TABLE IF NOT EXISTS enrollment (
        id TEXT PRIMARY KEY,
        student_id TEXT NOT NULL,
        course_id TEXT NOT NULL,
        enroll_time TEXT,
        status INTEGER DEFAULT 0,
        remark TEXT,
        create_time TEXT,
        update_time TEXT,
        FOREIGN KEY (student_id) REFERENCES student(id),
        FOREIGN KEY (course_id) REFERENCES course(id)
      )
    `;

    await this.rdbStore?.executeSql(createStudentTable);
    await this.rdbStore?.executeSql(createCourseTable);
    await this.rdbStore?.executeSql(createEnrollmentTable);
  }

  getRdbStore(): rdb.RdbStore | null {
    return this.rdbStore;
  }
}

5.3 工具类

// common/Utils.ets
export class Utils {
  /**
   * 生成唯一ID
   */
  static generateId(): string {
    return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  /**
   * 格式化日期
   */
  static formatDate(date: Date, format: string = 'YYYY-MM-DD HH:mm:ss'): string {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');

    return format
      .replace('YYYY', String(year))
      .replace('MM', month)
      .replace('DD', day)
      .replace('HH', hours)
      .replace('mm', minutes)
      .replace('ss', seconds);
  }

  /**
   * 验证手机号
   */
  static isValidPhone(phone: string): boolean {
    const phoneRegex = /^1[3-9]\d{9}$/;
    return phoneRegex.test(phone);
  }

  /**
   * 验证邮箱
   */
  static isValidEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  /**
   * 显示提示信息
   */
  static showToast(message: string): void {
    // 使用系统Toast API
    console.info(message);
  }
}

5.4 常量定义

// common/Constants.ets
export class Constants {
  // 数据库版本
  static readonly DB_VERSION = 1;

  // 性别常量
  static readonly GENDER_UNKNOWN = 0;
  static readonly GENDER_MALE = 1;
  static readonly GENDER_FEMALE = 2;

  // 课程状态
  static readonly COURSE_STATUS_NOT_STARTED = 0;
  static readonly COURSE_STATUS_IN_PROGRESS = 1;
  static readonly COURSE_STATUS_ENDED = 2;

  // 报名状态
  static readonly ENROLLMENT_STATUS_ENROLLED = 0;
  static readonly ENROLLMENT_STATUS_ATTENDED = 1;
  static readonly ENROLLMENT_STATUS_REFUNDED = 2;

  // 页面路由
  static readonly PAGE_INDEX = 'pages/Index';
  static readonly PAGE_STUDENT_LIST = 'pages/StudentList';
  static readonly PAGE_STUDENT_DETAIL = 'pages/StudentDetail';
  static readonly PAGE_COURSE_LIST = 'pages/CourseList';
  static readonly PAGE_COURSE_DETAIL = 'pages/CourseDetail';
  static readonly PAGE_ENROLLMENT = 'pages/EnrollmentPage';
  static readonly PAGE_MY = 'pages/MyPage';
}

5.5 首页实现

// pages/Index.ets
import { Constants } from '../common/Constants';

@Entry
@Component
struct Index {
  @State currentIndex: number = 0;

  private tabItems: TabBarItem[] = [
    { title: '学员', icon: $r('app.media.ic_student') },
    { title: '课程', icon: $r('app.media.ic_course') },
    { title: '报名', icon: $r('app.media.ic_enrollment') },
    { title: '我的', icon: $r('app.media.ic_my') }
  ];

  build() {
    Column() {
      // 标题栏
      TitleBar({ title: '培训班管理系统' })

      // 内容区域
      Tabs({ barPosition: BarPosition.End }) {
        TabContent() {
          StudentList()
        }
        .tabBar(this.TabBarItem(0))

        TabContent() {
          CourseList()
        }
        .tabBar(this.TabBarItem(1))

        TabContent() {
          EnrollmentPage()
        }
        .tabBar(this.TabBarItem(2))

        TabContent() {
          MyPage()
        }
        .tabBar(this.TabBarItem(3))
      }
      .onChange((index: number) => {
        this.currentIndex = index;
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder
  TabBarItem(index: number) {
    Column() {
      Image(this.tabItems[index].icon)
        .width(24)
        .height(24)
        .fillColor(this.currentIndex === index ? '#007DFF' : '#999999')

      Text(this.tabItems[index].title)
        .fontSize(12)
        .fontColor(this.currentIndex === index ? '#007DFF' : '#999999')
        .margin({ top: 4 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

interface TabBarItem {
  title: string;
  icon: Resource;
}

5.6 标题栏组件

// components/common/TitleBar.ets
@Component
export struct TitleBar {
  @Prop title: string = '';
  @Prop showBack: boolean = false;
  backCallback: () => void = () => {};

  build() {
    Row() {
      // 返回按钮
      if (this.showBack) {
        Image($r('app.media.ic_back'))
          .width(24)
          .height(24)
          .onClick(() => {
            this.backCallback();
          })
      }

      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
        .layoutWeight(1)
        .textAlign(TextAlign.Center)
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    .backgroundColor('#FFFFFF')
  }
}

六、踩坑记录

6.1 数据库初始化问题

问题:数据库初始化时,表创建失败。

原因:SQL语句中的外键约束导致创建失败。

解决方案

// 先创建主表,再创建外键表
await this.rdbStore?.executeSql(createStudentTable);
await this.rdbStore?.executeSql(createCourseTable);
await this.rdbStore?.executeSql(createEnrollmentTable);

6.2 资源引用问题

问题:图片资源无法加载。

原因:资源路径配置错误。

解决方案

// 确保资源文件放在正确的目录
entry/src/main/resources/base/media/

6.3 组件通信问题

问题:父子组件数据不同步。

原因:@Prop装饰器是单向数据流。

解决方案

// 使用@Link实现双向数据绑定
@Link isRefresh: boolean;

七、总结与预告

本文要点回顾

  1. 需求分析:明确了培训班管理系统的功能需求和用户角色

  2. 架构设计:采用分层架构,分离UI、业务逻辑和数据层

  3. 目录规划:按照功能模块划分,提高代码可维护性

  4. 环境搭建:详细介绍了DevEco Studio的安装和配置

  5. 基础框架:实现了数据模型、数据库、工具类等基础代码

下期预告

下期我们将深入讲解UI界面篇,包括:

  • 学员列表页面实现

  • 课程详情页面设计

  • 报名页面交互优化

  • 响应式布局适配

互动引导

如果本文对你有帮助,请点赞、收藏、关注!有任何问题欢迎在评论区留言,我会及时回复。


系列文章导航

  • 第1篇:项目架构篇(本文)

  • 第2篇:UI界面篇

  • 第3篇:状态管理篇

  • 第4篇:数据持久化篇

  • 第5篇:性能优化篇

Logo

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

更多推荐