使用AppGallery Connect构建智能学习平台

基于HarmonyOS Next的教育类应用开发实战:使用AppGallery Connect构建智能学习平台

一、AppGallery Connect与教育应用开发概述

在当今数字化教育时代,移动应用已成为学习的重要工具。HarmonyOS Next作为华为推出的新一代操作系统,为教育类应用开发提供了强大的技术支持。AppGallery Connect作为华为的开发者服务平台,集成了多种服务能力,能够帮助开发者快速构建高质量的教育应用。

教育类应用通常需要具备以下核心功能:

  • 用户账户管理与学习进度同步
  • 课程内容管理与更新
  • 学习数据统计与分析
  • 互动功能如问答、测验等
  • 多设备协同学习体验

本教程将带领开发者使用ArkTS语言,基于AppGallery Connect服务,构建一个完整的智能学习平台应用。我们将从基础架构开始,逐步实现核心功能模块。

二、开发环境准备与项目初始化

在开始编码前,我们需要完成开发环境的准备工作:

  1. 安装最新版DevEco Studio(建议4.0或以上版本)
  2. 注册华为开发者账号并完成实名认证
  3. 在AppGallery Connect中创建新项目并启用所需服务

项目初始化代码

// 项目入口文件:entry/src/main/ets/entryability/EntryAbility.ts
import Ability from **********';
import window from **********';

export default class EntryAbility extends Ability {
  onCreate(want, launchParam) {
    console.info('EntryAbility onCreate');
    // 初始化AppGallery Connect服务
    this.initAGC();
  }

  private async initAGC() {
    try {
      // 引入AGC核心模块
      const agconnect = await import('@hw-agconnect/core-ohos');
      // 使用项目配置初始化AGC
      agconnect.default.instance().init(this.context);
      console.info('AGC初始化成功');
    } catch (error) {
      console.error('AGC初始化失败:', error);
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // 设置主页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err) {
        console.error('加载页面失败:', err);
      }
    });
  }
}

三、用户认证与学习数据同步

教育应用通常需要用户系统来保存学习进度和个人数据。AppGallery Connect提供了完善的认证服务和云数据库功能。

1. 用户认证模块实现

// src/main/ets/model/UserModel.ts
import { agconnect } from '@hw-agconnect/core-ohos';
import { AGCAuth, AGConnectUser } from '@hw-agconnect/auth-ohos';

export class UserModel {
  // 获取当前用户
  static getCurrentUser(): Promise<AGConnectUser | null> {
    return AGCAuth.getInstance().getCurrentUser();
  }

  // 匿名登录
  static async anonymousLogin(): Promise<AGConnectUser> {
    try {
      const user = await AGCAuth.getInstance().signInAnonymously();
      console.info('匿名登录成功:', user.getUid());
      return user;
    } catch (error) {
      console.error('匿名登录失败:', error);
      throw error;
    }
  }

  // 邮箱注册
  static async registerWithEmail(email: string, password: string): Promise<AGConnectUser> {
    try {
      const user = await AGCAuth.getInstance().createEmailUser(email, password);
      console.info('邮箱注册成功:', user.getUid());
      return user;
    } catch (error) {
      console.error('邮箱注册失败:', error);
      throw error;
    }
  }
  
  // 邮箱登录
  static async loginWithEmail(email: string, password: string): Promise<AGConnectUser> {
    try {
      const user = await AGCAuth.getInstance().signInWithEmailAndPassword(email, password);
      console.info('邮箱登录成功:', user.getUid());
      return user;
    } catch (error) {
      console.error('邮箱登录失败:', error);
      throw error;
    }
  }
}

2. 学习进度同步功能

// src/main/ets/model/ProgressModel.ts
import { agconnect } from '@hw-agconnect/core-ohos';
import { AGCCloudDB, CloudDBZone, CloudDBZoneConfig, CloudDBZoneQuery } from '@hw-agconnect/clouddb-ohos';
import { UserModel } from './UserModel';

// 定义学习进度数据结构
interface LearningProgress {
  id: string; // 文档ID
  userId: string; // 用户ID
  courseId: string; // 课程ID
  progress: number; // 学习进度0-100
  lastUpdate: number; // 最后更新时间戳
  notes?: string; // 学习笔记
}

export class ProgressModel {
  private cloudDBZone: CloudDBZone | null = null;
  
  // 初始化云数据库
  async initCloudDB(): Promise<void> {
    try {
      const agcCloudDB = AGCCloudDB.getInstance();
      const cloudDBZoneConfig = new CloudDBZoneConfig('LearningDB', CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE);
      this.cloudDBZone = await agcCloudDB.openCloudDBZone(cloudDBZoneConfig);
      console.info('云数据库初始化成功');
    } catch (error) {
      console.error('云数据库初始化失败:', error);
      throw error;
    }
  }
  
  // 保存学习进度
  async saveProgress(courseId: string, progress: number, notes?: string): Promise<void> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    const user = await UserModel.getCurrentUser();
    if (!user) {
      throw new Error('用户未登录');
    }
    
    const progressData: LearningProgress = {
      id: `${user.getUid()}_${courseId}`,
      userId: user.getUid(),
      courseId,
      progress,
      lastUpdate: new Date().getTime(),
      notes
    };
    
    try {
      await this.cloudDBZone!.executeUpsert(progressData);
      console.info('学习进度保存成功');
    } catch (error) {
      console.error('学习进度保存失败:', error);
      throw error;
    }
  }
  
  // 获取学习进度
  async getProgress(courseId: string): Promise<LearningProgress | null> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    const user = await UserModel.getCurrentUser();
    if (!user) {
      throw new Error('用户未登录');
    }
    
    try {
      const query = CloudDBZoneQuery.where(LearningProgress).equalTo('id', `${user.getUid()}_${courseId}`);
      const result = await this.cloudDBZone!.executeQuery(query);
      return result.length > 0 ? result[0] : null;
    } catch (error) {
      console.error('获取学习进度失败:', error);
      throw error;
    }
  }
}

四、课程内容管理模块

教育应用的核心是课程内容的管理与展示。我们可以使用AppGallery Connect的云存储和云函数服务来实现动态课程更新。

1. 课程数据结构定义

// src/main/ets/model/CourseModel.ts
import { agconnect } from '@hw-agconnect/core-ohos';
import { AGCCloudDB, CloudDBZone, CloudDBZoneConfig, CloudDBZoneQuery } from '@hw-agconnect/clouddb-ohos';
import { AGCCloudStorage, UploadTask, DownloadTask } from '@hw-agconnect/storage-ohos';

// 课程数据结构
interface Course {
  id: string; // 课程ID
  title: string; // 课程标题
  description: string; // 课程描述
  coverUrl: string; // 封面图URL
  category: string; // 课程分类
  duration: number; // 课程时长(分钟)
  difficulty: number; // 难度等级1-5
  createTime: number; // 创建时间戳
  updateTime: number; // 更新时间戳
  isFree: boolean; // 是否免费
}

// 课程章节结构
interface Chapter {
  id: string; // 章节ID
  courseId: string; // 所属课程ID
  title: string; // 章节标题
  order: number; // 章节顺序
  videoUrl?: string; // 视频URL
  content?: string; // 章节内容(HTML格式)
  duration: number; // 章节时长(分钟)
}

export class CourseModel {
  private cloudDBZone: CloudDBZone | null = null;
  private storage = AGCCloudStorage.getInstance();
  
  // 初始化云数据库
  async initCloudDB(): Promise<void> {
    if (this.cloudDBZone) return;
    
    try {
      const agcCloudDB = AGCCloudDB.getInstance();
      const cloudDBZoneConfig = new CloudDBZoneConfig('LearningDB', CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE);
      this.cloudDBZone = await agcCloudDB.openCloudDBZone(cloudDBZoneConfig);
      console.info('云数据库初始化成功');
    } catch (error) {
      console.error('云数据库初始化失败:', error);
      throw error;
    }
  }
  
  // 获取课程列表
  async getCourseList(category?: string): Promise<Course[]> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    try {
      let query = CloudDBZoneQuery.where(Course);
      if (category) {
        query = query.equalTo('category', category);
      }
      query = query.orderByDesc('updateTime');
      return await this.cloudDBZone!.executeQuery(query);
    } catch (error) {
      console.error('获取课程列表失败:', error);
      throw error;
    }
  }
  
  // 获取课程详情
  async getCourseDetail(courseId: string): Promise<{course: Course, chapters: Chapter[]}> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    try {
      // 查询课程信息
      const courseQuery = CloudDBZoneQuery.where(Course).equalTo('id', courseId);
      const courseResult = await this.cloudDBZone!.executeQuery(courseQuery);
      if (courseResult.length === 0) {
        throw new Error('课程不存在');
      }
      
      // 查询章节信息
      const chapterQuery = CloudDBZoneQuery.where(Chapter)
        .equalTo('courseId', courseId)
        .orderByAsc('order');
      const chapters = await this.cloudDBZone!.executeQuery(chapterQuery);
      
      return {
        course: courseResult[0],
        chapters
      };
    } catch (error) {
      console.error('获取课程详情失败:', error);
      throw error;
    }
  }
  
  // 上传课程封面图
  async uploadCourseCover(fileUri: string): Promise<string> {
    try {
      // 生成唯一文件名
      const timestamp = new Date().getTime();
      const fileName = `course_covers/${timestamp}.jpg`;
      
      // 创建上传任务
      const uploadTask: UploadTask = this.storage.uploadFile({
        path: fileName,
        fileUri: fileUri
      });
      
      // 等待上传完成
      await uploadTask;
      
      // 获取下载URL
      const downloadUrl = await this.storage.getDownloadUrl({ path: fileName });
      return downloadUrl;
    } catch (error) {
      console.error('上传课程封面失败:', error);
      throw error;
    }
  }
}

2. 课程展示UI实现

// src/main/ets/pages/CourseListPage.ets
@Component
struct CourseListPage {
  @State courseList: Course[] = [];
  @State isLoading: boolean = true;
  @State selectedCategory: string = 'all';

  private courseModel = new CourseModel();

  aboutToAppear() {
    this.loadCourses();
  }

  private async loadCourses() {
    this.isLoading = true;
    try {
      const category = this.selectedCategory === 'all' ? undefined : this.selectedCategory;
      this.courseList = await this.courseModel.getCourseList(category);
    } catch (error) {
      console.error('加载课程失败:', error);
      // 这里可以添加错误提示UI
    } finally {
      this.isLoading = false;
    }
  }

  build() {
    Column() {
      // 分类筛选
      Segmented({ barPosition: BarPosition.Start }) {
        ForEach(['all', 'programming', 'language', 'math', 'science'], (item: string) => {
          Segment(item.toUpperCase())
            .fontSize(14)
            .fontWeight(FontWeight.Medium)
        })
      }
      .margin(10)
      .onChange((index: number) => {
        this.selectedCategory = ['all', 'programming', 'language', 'math', 'science'][index];
        this.loadCourses();
      })

      // 课程列表
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else {
        Grid() {
          ForEach(this.courseList, (course: Course) => {
            GridItem() {
              CourseCard({ course: course })
            }
          }, (course: Course) => course.id)
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(10)
        .rowsGap(10)
        .padding(10)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

@Component
struct CourseCard {
  private course: Course;

  build() {
    Column() {
      // 课程封面
      Image(this.course.coverUrl)
        .width('100%')
        .aspectRatio(1.5)
        .objectFit(ImageFit.Cover)
        .borderRadius(8)

      // 课程标题
      Text(this.course.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .margin({ top: 8, bottom: 4 })
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })

      // 课程描述
      Text(this.course.description)
        .fontSize(12)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ bottom: 8 })

      // 难度和时长
      Row() {
        // 难度星级
        ForEach(Array.from({ length: this.course.difficulty }), (_, index) => {
          Image($r('app.media.star_filled'))
            .width(12)
            .height(12)
            .margin({ right: 2 })
        })

        // 时长
        Text(`${this.course.duration}分钟`)
          .fontSize(12)
          .margin({ left: 8 })
      }
    }
    .padding(10)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .shadow({ radius: 4, color: '#00000020', offsetX: 0, offsetY: 2 })
  }
}

五、学习互动与测验功能

教育应用的互动性对学习效果至关重要。下面我们实现一个测验模块,包含题目管理和答题功能。

1. 测验数据结构与模型

// src/main/ets/model/QuizModel.ts
import { agconnect } from '@hw-agconnect/core-ohos';
import { AGCCloudDB, CloudDBZone, CloudDBZoneConfig, CloudDBZoneQuery } from '@hw-agconnect/clouddb-ohos';
import { UserModel } from './UserModel';

// 测验题目结构
interface QuizQuestion {
  id: string; // 题目ID
  courseId: string; // 所属课程ID
  chapterId?: string; // 所属章节ID(可选)
  questionType: 'single' | 'multiple' | 'true_false' | 'fill_blank'; // 题目类型
  questionText: string; // 题目文本
  options?: string[]; // 选项(选择题使用)
  correctAnswers: string[]; // 正确答案
  explanation?: string; // 答案解析
  difficulty: number; // 难度1-5
  createTime: number; // 创建时间
}

// 用户答题记录
interface UserQuizRecord {
  id: string; // 记录ID
  userId: string; // 用户ID
  questionId: string; // 题目ID
  userAnswers: string[]; // 用户答案
  isCorrect: boolean; // 是否正确
  answerTime: number; // 答题时间
}

export class QuizModel {
  private cloudDBZone: CloudDBZone | null = null;
  
  async initCloudDB(): Promise<void> {
    if (this.cloudDBZone) return;
    
    try {
      const agcCloudDB = AGCCloudDB.getInstance();
      const cloudDBZoneConfig = new CloudDBZoneConfig('LearningDB', CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE);
      this.cloudDBZone = await agcCloudDB.openCloudDBZone(cloudDBZoneConfig);
      console.info('云数据库初始化成功');
    } catch (error) {
      console.error('云数据库初始化失败:', error);
      throw error;
    }
  }
  
  // 获取课程测验题目
  async getQuizQuestions(courseId: string, chapterId?: string): Promise<QuizQuestion[]> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    try {
      let query = CloudDBZoneQuery.where(QuizQuestion)
        .equalTo('courseId', courseId)
        .orderByAsc('createTime');
      
      if (chapterId) {
        query = query.equalTo('chapterId', chapterId);
      }
      
      return await this.cloudDBZone!.executeQuery(query);
    } catch (error) {
      console.error('获取测验题目失败:', error);
      throw error;
    }
  }
  
  // 提交用户答案
  async submitAnswer(questionId: string, userAnswers: string[]): Promise<boolean> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    const user = await UserModel.getCurrentUser();
    if (!user) {
      throw new Error('用户未登录');
    }
    
    try {
      // 先获取题目信息
      const questionQuery = CloudDBZoneQuery.where(QuizQuestion).equalTo('id', questionId);
      const questions = await this.cloudDBZone!.executeQuery(questionQuery);
      if (questions.length === 0) {
        throw new Error('题目不存在');
      }
      
      const question = questions[0];
      // 判断答案是否正确
      const isCorrect = JSON.stringify(userAnswers.sort()) === JSON.stringify(question.correctAnswers.sort());
      
      // 保存答题记录
      const record: UserQuizRecord = {
        id: `${user.getUid()}_${questionId}_${new Date().getTime()}`,
        userId: user.getUid(),
        questionId,
        userAnswers,
        isCorrect,
        answerTime: new Date().getTime()
      };
      
      await this.cloudDBZone!.executeUpsert(record);
      console.info('答题记录保存成功');
      
      return isCorrect;
    } catch (error) {
      console.error('提交答案失败:', error);
      throw error;
    }
  }
  
  // 获取用户答题统计
  async getUserQuizStats(courseId: string): Promise<{total: number, correct: number}> {
    if (!this.cloudDBZone) {
      await this.initCloudDB();
    }
    
    const user = await UserModel.getCurrentUser();
    if (!user) {
      throw new Error('用户未登录');
    }
    
    try {
      // 获取课程所有题目ID
      const questionQuery = CloudDBZoneQuery.where(QuizQuestion).equalTo('courseId', courseId);
      const questions = await this.cloudDBZone!.executeQuery(questionQuery);
      const questionIds = questions.map(q => q.id);
      
      if (questionIds.length === 0) {
        return { total: 0, correct: 0 };
      }
      
      // 查询用户答题记录
      const recordQuery = CloudDBZoneQuery.where(UserQuizRecord)
        .equalTo('userId', user.getUid())
        .in('questionId', questionIds);
      
      const records = await this.cloudDBZone!.executeQuery(recordQuery);
      
      // 统计正确率
      const correctCount = records.filter(r => r.isCorrect).length;
      
      return {
        total: records.length,
        correct: correctCount
      };
    } catch (error) {
      console.error('获取答题统计失败:', error);
      throw error;
    }
  }
}

2. 测验UI实现

// src/main/ets/pages/QuizPage.ets
@Component
struct QuizPage {
  @State questions: QuizQuestion[] = [];
  @State currentIndex: number = 0;
  @State selectedAnswers: string[] = [];
  @State showResult: boolean = false;
  @State isCorrect: boolean = false;
  @State isLoading: boolean = true;
  
  private courseId: string;
  private quizModel = new QuizModel();
  
  aboutToAppear() {
    this.loadQuestions();
  }
  
  private async loadQuestions() {
    this.isLoading = true;
    try {
      this.questions = await this.quizModel.getQuizQuestions(this.courseId);
      this.isLoading = false;
    } catch (error) {
      console.error('加载题目失败:', error);
      this.isLoading = false;
      // 可以添加错误提示
    }
  }
  
  private async submitAnswer() {
    if (this.selectedAnswers.length === 0) {
      // 提示用户选择答案
      return;
    }
    
    try {
      const currentQuestion = this.questions[this.currentIndex];
      this.isCorrect = await this.quizModel.submitAnswer(currentQuestion.id, this.selectedAnswers);
      this.showResult = true;
    } catch (error) {
      console.error('提交答案失败:', error);
      // 可以添加错误提示
    }
  }
  
  private nextQuestion() {
    this.showResult = false;
    this.selectedAnswers = [];
    if (this.currentIndex < this.questions.length - 1) {
      this.currentIndex++;
    } else {
      // 测验完成,可以跳转到结果页面
      // 或显示总结信息
    }
  }
  
  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else if (this.questions.length === 0) {
        Text('暂无测验题目')
          .fontSize(16)
      } else {
        // 题目进度
        Text(`题目 ${this.currentIndex + 1}/${this.questions.length}`)
          .fontSize(14)
          .margin({ top: 10, bottom: 5 })
        
        // 题目内容
        Scroll() {
          Column() {
            // 题目文本
            Text(this.questions[this.currentIndex].questionText)
              .fontSize(18)
              .fontWeight(FontWeight.Medium)
              .margin({ bottom: 20 })
            
            // 根据题目类型显示不同答题UI
            if (this.questions[this.currentIndex].questionType === 'single' || 
                this.questions[this.currentIndex].questionType === 'multiple') {
              // 单选题或多选题
              ForEach(this.questions[this.currentIndex].options, (option: string, index: number) => {
                Button(option)
                  .width('90%')
                  .margin({ bottom: 10 })
                  .stateEffect(!this.showResult)
                  .backgroundColor(
                    this.showResult && this.questions[this.currentIndex].correctAnswers.includes(index.toString()) 
                      ? '#4CAF50' 
                      : this.showResult && this.selectedAnswers.includes(index.toString()) && !this.isCorrect
                        ? '#F44336'
                        : this.selectedAnswers.includes(index.toString())
                          ? '#2196F3'
                          : '#FFFFFF'
                  )
                  .onClick(() => {
                    if (this.showResult) return;
                    
                    const answer = index.toString();
                    if (this.questions[this.currentIndex].questionType === 'single') {
                      this.selectedAnswers = [answer];
                    } else {
                      if (this.selectedAnswers.includes(answer)) {
                        this.selectedAnswers = this.selectedAnswers.filter(a => a !== answer);
                      } else {
                        this.selectedAnswers = [...this.selectedAnswers, answer];
                      }
                    }
                  })
              })
            } else if (this.questions[this.currentIndex].questionType === 'true_false') {
              // 判断题
              Row() {
                Button('正确')
                  .width('40%')
                  .backgroundColor(
                    this.showResult && this.questions[this.currentIndex].correctAnswers.includes('true')
                      ? '#4CAF50'
                      : this.showResult && this.selectedAnswers.includes('true') && !this.isCorrect
                        ? '#F44336'
                        : this.selectedAnswers.includes('true')
                          ? '#2196F3'
                          : '#FFFFFF'
                  )
                  .onClick(() => {
                    if (!this.showResult) this.selectedAnswers = ['true'];
                  })
                
                Button('错误')
                  .width('40%')
                  .margin({ left: 20 })
                  .backgroundColor(
                    this.showResult && this.questions[this.currentIndex].correctAnswers.includes('false')
                      ? '#4CAF50'
                      : this.showResult && this.selectedAnswers.includes('false') && !this.isCorrect
                        ? '#F44336'
                        : this.selectedAnswers.includes('false')
                          ? '#2196F3'
                          : '#FFFFFF'
                  )
                  .onClick(() => {
                    if (!this.showResult) this.selectedAnswers = ['false'];
                  })
              }
              .width('100%')
              .justifyContent(FlexAlign.Center)
            } else {
              // 填空题
              TextInput({ placeholder: '请输入答案' })
                .width('90%')
                .height(100)
                .margin({ bottom: 20 })
                .onChange((value: string) => {
                  this.selectedAnswers = [value];
                })
            }
            
            // 答案解析
            if (this.showResult && this.questions[this.currentIndex].explanation) {
              Text('解析: ' + this.questions[this.currentIndex].explanation)
                .fontSize(14)
                .margin({ top: 20, bottom: 10 })
                .fontColor('#666666')
            }
          }
          .padding(20)
        }
        .height('70%')
        
        // 提交/下一题按钮
        Button(this.showResult ? '下一题' : '提交答案')
          .width('80%')
          .height(50)
          .margin({ top: 20 })
          .onClick(() => {
            if (this.showResult) {
              this.nextQuestion();
            } else {
              this.submitAnswer();
            }
          })
      }
    }
    .width('100%')
    .height('100%')
  }
}

六、学习数据分析与可视化

教育应用的学习数据分析功能可以帮助用户了解自己的学习情况。我们可以使用AppGallery Connect的分析服务来实现这一功能。

1. 学习数据统计实现

// src/main/ets/model/AnalyticsModel.ts
import { agconnect } from '@hw-agconnect/core-ohos';
import { AGCAnalytics } from '@hw-agconnect/analytics-ohos';
import { UserModel } from './UserModel';

export class AnalyticsModel {
  // 记录学习行为
  static async logLearningEvent(courseId: string, chapterId: string, duration: number): Promise<void> {
    try {
      const user = await UserModel.getCurrentUser();
      const userId = user ? user.getUid() : 'anonymous';
      
      AGCAnalytics.getInstance().logEvent('learning_event', {
        user_id: userId,
        course_id: courseId,
        chapter_id: chapterId,
        duration: duration
      });
      console.info('学习行为记录成功');
    } catch (error) {
      console.error('学习行为记录失败:', error);
    }
  }
  
  // 获取用户学习统计数据
  static async getUserLearningStats(userId: string): Promise<{
    totalLearningTime: number,
    courseProgress: Record<string, number>,
    dailyLearning: Record<string, number>
  }> {
    // 注意: 实际应用中需要通过云函数获取分析数据
    // 这里为简化示例返回模拟数据
    return {
      totalLearningTime: 1250, // 分钟
      courseProgress: {
        'course_1': 65,
        'course_2': 30,
        'course_3': 90
      },
      dailyLearning: {
        '2023-11-01': 45,
        '2023-11-02': 60,
        '2023-11-03': 30,
        '2023-11-04': 90,
        '2023-11-05': 45
      }
    };
  }
}

2. 学习数据可视化UI

// src/main/ets/pages/AnalyticsPage.ets
@Component
struct AnalyticsPage {
  @State learningStats: {
    totalLearningTime: number,
    courseProgress: Record<string, number>,
    dailyLearning: Record<string, number>
  } | null = null;
  @State isLoading: boolean = true;
  
  private analyticsModel = new AnalyticsModel();
  
  aboutToAppear() {
    this.loadLearningStats();
  }
  
  private async loadLearningStats() {
    this.isLoading = true;
    try {
      const user = await UserModel.getCurrentUser();
      if (!user) {
        throw new Error('用户未登录');
      }
      this.learningStats = await AnalyticsModel.getUserLearningStats(user.getUid());
      this.isLoading = false;
    } catch (error) {
      console.error('获取学习数据失败:', error);
      this.isLoading = false;
    }
  }
  
  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else if (!this.learningStats) {
        Text('无法获取学习数据')
          .fontSize(16)
      } else {
        // 总学习时间
        Row() {
          Text('总学习时间:')
            .fontSize(16)
          Text(`${Math.floor(this.learningStats.totalLearningTime / 60)}小时${this.learningStats.totalLearningTime % 60}分钟`)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .margin({ left: 10 })
        }
        .margin({ top: 20, bottom: 20 })
        
        // 课程进度图表
        Text('课程进度')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .margin({ bottom: 10 })
        
        ForEach(Object.entries(this.learningStats.courseProgress), ([courseId, progress]) => {
          Column() {
            Row() {
              Text(`课程 ${courseId.split('_')[1]}`)
                .width('20%')
                .fontSize(14)
              
              Progress({
                value: progress,
                total: 100,
                type: ProgressType.Linear
              })
                .width('70%')
                .height(20)
                .margin({ left: 10 })
              
              Text(`${progress}%`)
                .fontSize(14)
                .margin({ left: 10 })
            }
            .margin({ bottom: 5 })
          }
        })
        
        // 每日学习时间图表
        Text('最近学习情况')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .margin({ top: 20, bottom: 10 })
        
        Row() {
          ForEach(Object.entries(this.learningStats.dailyLearning), ([date, minutes]) => {
            Column() {
              // 柱状图
              Column() {
                Blank()
              }
              .width(30)
              .height(minutes)
              .backgroundColor('#2196F3')
              .borderRadius(4)
              
              // 日期
              Text(date.split('-')[2])
                .fontSize(12)
                .margin({ top: 5 })
            }
            .margin({ right: 5 })
            .alignItems(HorizontalAlign.Center)
          })
        }
        .height(150)
        .margin({ top: 10 })
        .justifyContent(FlexAlign.End)
        .alignItems(VerticalAlign.Bottom)
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

七、应用发布与持续集成

完成开发后,我们需要将应用发布到AppGallery。AppGallery Connect提供了完善的发布和持续集成能力。

1. 应用签名配置

build-profile.json5中添加签名配置:

{
  "app": {
    "signingConfigs": [
      {
        "name": "release",
        "material": {
          "certpath": "signing/your_certificate.pem",
          "storePassword": "your_store_password",
          "keyAlias": "your_key_alias",
          "keyPassword": "your_key_password",
          "profile": "signing/your_profile.p7b",
          "signAlg": "SHA256withECDSA",
          "storeFile": "signing/your_keystore.jks"
        }
      }
    ],
    "buildType": "release"
  }
}

2. 持续集成配置

在项目根目录创建.github/workflows/build.yml文件:

name: HarmonyOS CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up JDK
      uses: actions/setup-java@v1
      with:
        java-version: '11'
    
    - name: Build with Gradle
      run: ./gradlew assembleRelease
    
    - name: Upload to AppGallery Connect
      uses: **********
      with:
        clientId: ${{ secrets.AGC_CLIENT_ID }}
        clientSecret: ${{ secrets.AGC_CLIENT_SECRET }}
        appId: ${{ secrets.AGC_APP_ID }}
        filePath: build/outputs/hap/release/your-app-release.hap
        phaseId: ${{ secrets.AGC_PHASE_ID }}

八、总结与进阶方向

通过本教程,我们完成了一个基于HarmonyOS Next和AppGallery Connect的完整教育类应用开发。我们实现了:

  1. 用户认证与数据同步
  2. 课程内容管理与展示
  3. 学习进度跟踪
  4. 互动测验功能
  5. 学习数据分析与可视化

进阶开发方向

  1. 多设备协同学习:利用HarmonyOS的分布式能力,实现手机、平板、智慧屏等多设备间的学习进度同步和协同学习体验。
  2. AI个性化推荐:集成华为机器学习服务,根据用户学习行为和能力水平,智能推荐适合的学习内容和路径。
  3. 实时互动课堂:使用实时音视频服务,构建在线直播课堂功能,支持师生实时互动。
  4. 离线学习支持:增强应用的离线能力,允许用户下载课程内容在无网络环境下学习。
  5. 学习社区功能:增加社交元素,构建学习社区,支持用户间交流讨论。

HarmonyOS Next与AppGallery Connect的结合为教育应用开发提供了强大的基础设施和服务支持,开发者可以专注于创新教育体验的实现,快速构建高质量的教育类应用。

全部评论

相关推荐

投递阿里巴巴控股集团等公司9个岗位 今年秋招哪家公司给的薪资最良心?
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务