HarmonyOS智慧农业管理应用开发教程--高高种地--第27篇:考试系统 - 成绩分析与错题

第27篇:考试系统 - 成绩分析与错题

📚 本篇导读

在上一篇中,我们实现了考试系统的题库管理和答题流程。本篇将完善考试系统的后续功能,包括成绩详情展示、错题分析、历史记录管理等,帮助用户更好地了解学习效果和薄弱环节。

本篇将实现

  • 📊 成绩详情展示(得分、正确率、用时统计)
  • 📈 答题统计分析(知识点分析、薄弱环节)
  • 错题本功能(错题收集、解析查看)
  • 📝 答案解析展示(详细解释、知识点说明)
  • 📋 历史记录管理(考试记录、成绩趋势)

🎯 学习目标

完成本篇教程后,你将掌握:

  1. 如何设计成绩展示页面
  2. 如何实现错题分析功能
  3. 如何展示答案解析
  4. 如何管理历史记录
  5. 数据统计和可视化展示

一、成绩结果页面

1.1 页面结构设计

复制代码
成绩结果页
├── 成绩概览
│   ├── 通过状态(通过/未通过)
│   ├── 总分显示
│   ├── 答题统计(总题数、正确数、错误数)
│   └── 用时统计
│
├── 操作按钮
│   ├── 查看错题解析
│   ├── 重新考试
│   └── 返回考试中心
│
└── 详细分析
    ├── 错题列表
    ├── 答案解析
    ├── 知识点分析
    └── 薄弱环节提示

1.2 成绩展示页面

文件位置entry/src/main/ets/pages/Exam/ExamResultPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { ExamRecord, ExamQuestion, UserAnswer } from '../../models/ExamModels';

@Entry
@Component
export struct ExamResultPage {
  @State record: ExamRecord | null = null;
  @State questions: ExamQuestion[] = [];
  @State showAnalysis: boolean = false;

  aboutToAppear(): void {
    const params = router.getParams() as Record<string, Object>;
    this.record = params['record'] as ExamRecord;
    this.questions = params['questions'] as ExamQuestion[];
  }

  build() {
    Column() {
      if (this.record) {
        if (!this.showAnalysis) {
          this.buildResultSummary()
        } else {
          this.buildAnalysisView()
        }
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
  }
}

1.3 成绩概览卡片

typescript 复制代码
@Builder
buildResultSummary() {
  Column() {
    // 顶部结果卡片
    Column() {
      // 通过/未通过图标
      Text(this.record!.passed ? '🎉' : '😔')
        .fontSize(64)
        .margin({ bottom: 16 })

      Text(this.record!.passed ? '恭喜通过!' : '继续加油!')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor($r('app.color.text_primary'))
        .margin({ bottom: 8 })

      Text(`得分:${this.record!.score}分`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.record!.passed ? '#4CAF50' : '#F44336')
        .margin({ bottom: 24 })

      // 统计信息
      Row({ space: 32 }) {
        this.buildStatItem('总题数', this.record!.totalQuestions.toString())
        this.buildStatItem('正确', this.record!.correctCount.toString(), '#4CAF50')
        this.buildStatItem('错误', 
          (this.record!.totalQuestions - this.record!.correctCount).toString(), '#F44336')
      }
      .margin({ bottom: 24 })

      // 用时
      Text(`用时:${this.formatDuration(this.record!.endTime - this.record!.startTime)}`)
        .fontSize(14)
        .fontColor($r('app.color.text_secondary'))
    }
    .width('100%')
    .padding(32)
    .backgroundColor($r('app.color.card_background'))
    .borderRadius(16)
    .margin(16)

    // 操作按钮
    this.buildActionButtons()
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
}

@Builder
buildStatItem(label: string, value: string, color?: string) {
  Column({ space: 4 }) {
    Text(value)
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .fontColor(color || $r('app.color.text_primary'))
    
    Text(label)
      .fontSize(12)
      .fontColor($r('app.color.text_tertiary'))
  }
}

@Builder
buildActionButtons() {
  Column({ space: 12 }) {
    Button('查看错题解析')
      .width('80%')
      .height(48)
      .fontSize(16)
      .backgroundColor($r('app.color.primary_professional'))
      .onClick(() => {
        this.showAnalysis = true;
      })

    Button('重新考试')
      .width('80%')
      .height(48)
      .fontSize(16)
      .backgroundColor($r('app.color.card_background'))
      .fontColor($r('app.color.text_primary'))
      .onClick(() => {
        this.retakeExam();
      })

    Button('返回考试中心')
      .width('80%')
      .height(48)
      .fontSize(16)
      .backgroundColor($r('app.color.card_background'))
      .fontColor($r('app.color.text_primary'))
      .onClick(() => {
        router.back();
      })
  }
  .width('100%')
  .padding(16)
}

/**
 * 格式化时长
 */
private formatDuration(milliseconds: number): string {
  const minutes = Math.floor(milliseconds / 60000);
  const seconds = Math.floor((milliseconds % 60000) / 1000);
  return `${minutes}分${seconds}秒`;
}

/**
 * 重新考试
 */
private retakeExam(): void {
  router.replaceUrl({
    url: 'pages/Exam/ExamQuestionPage',
    params: { level: this.record!.level }
  });
}

二、错题分析功能

2.1 错题分析视图

typescript 复制代码
@Builder
buildAnalysisView() {
  Column() {
    // 顶部导航
    Row() {
      Text('< 返回成绩')
        .fontSize(16)
        .fontColor($r('app.color.primary_professional'))
        .onClick(() => {
          this.showAnalysis = false;
        })

      Blank()

      Text('错题解析')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)

      Blank()

      Text('   ')
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    .backgroundColor($r('app.color.card_background'))

    // 错题统计
    this.buildErrorStats()

    // 错题列表
    Scroll() {
      Column({ space: 16 }) {
        ForEach(this.getWrongAnswers(), (answer: UserAnswer, index: number) => {
          this.buildErrorQuestionCard(answer, index)
        })
      }
      .padding(16)
    }
    .layoutWeight(1)
  }
  .width('100%')
  .height('100%')
}

@Builder
buildErrorStats() {
  const wrongCount = this.getWrongAnswers().length;
  const totalCount = this.record!.totalQuestions;
  const correctRate = Math.round((this.record!.correctCount / totalCount) * 100);

  Row({ space: 24 }) {
    Column({ space: 4 }) {
      Text(`${wrongCount}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#F44336')
      Text('错题数')
        .fontSize(12)
        .fontColor($r('app.color.text_tertiary'))
    }

    Column({ space: 4 }) {
      Text(`${correctRate}%`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#4CAF50')
      Text('正确率')
        .fontSize(12)
        .fontColor($r('app.color.text_tertiary'))
    }

    Column({ space: 4 }) {
      Text(this.getMostWeakCategory())
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FF9800')
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
      Text('薄弱环节')
        .fontSize(12)
        .fontColor($r('app.color.text_tertiary'))
    }
    .layoutWeight(1)
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .margin({ left: 16, right: 16, top: 16 })
  .borderRadius(12)
}

/**
 * 获取错题列表
 */
private getWrongAnswers(): UserAnswer[] {
  if (!this.record) return [];
  return this.record.answers.filter(answer => !answer.isCorrect);
}

/**
 * 获取最薄弱的知识点
 */
private getMostWeakCategory(): string {
  const wrongAnswers = this.getWrongAnswers();
  if (wrongAnswers.length === 0) return '无';

  const categoryCount: Record<string, number> = {};
  
  wrongAnswers.forEach(answer => {
    const question = this.questions.find(q => q.id === answer.questionId);
    if (question) {
      categoryCount[question.category] = (categoryCount[question.category] || 0) + 1;
    }
  });

  let maxCount = 0;
  let weakestCategory = '无';
  
  Object.entries(categoryCount).forEach(([category, count]) => {
    if (count > maxCount) {
      maxCount = count;
      weakestCategory = category;
    }
  });

  return weakestCategory;
}

2.2 错题卡片展示

typescript 复制代码
@Builder
buildErrorQuestionCard(answer: UserAnswer, index: number) {
  const question = this.questions.find(q => q.id === answer.questionId);
  if (!question) return;

  Column({ space: 12 }) {
    // 题目标题
    Row() {
      Text(`错题 ${index + 1}`)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#F44336')
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor('#FFEBEE')
        .borderRadius(12)

      Blank()

      Text(question.category)
        .fontSize(12)
        .fontColor($r('app.color.text_tertiary'))
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .backgroundColor($r('app.color.background'))
        .borderRadius(10)
    }
    .width('100%')

    // 题目内容
    Text(question.question)
      .fontSize(15)
      .fontColor($r('app.color.text_primary'))
      .lineHeight(24)
      .width('100%')

    // 选项展示
    ForEach(question.options, (option: QuestionOption) => {
      this.buildAnalysisOption(option, question.correctAnswer, answer.selectedAnswer)
    })

    // 答案解析
    Column({ space: 8 }) {
      Text('📝 答案解析')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor($r('app.color.text_primary'))
        .width('100%')

      Text(question.explanation)
        .fontSize(14)
        .fontColor($r('app.color.text_secondary'))
        .lineHeight(22)
        .width('100%')

      Row({ space: 8 }) {
        Text('💡 知识点:')
          .fontSize(12)
          .fontColor($r('app.color.text_tertiary'))

        Text(question.knowledgePoint)
          .fontSize(12)
          .fontColor($r('app.color.primary_professional'))
      }
      .width('100%')
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#F8F9FA')
    .borderRadius(8)
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
  .shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
}

@Builder
buildAnalysisOption(option: QuestionOption, correctAnswer: string, selectedAnswer: string) {
  const isCorrect = option.id === correctAnswer;
  const isSelected = option.id === selectedAnswer;
  const isWrong = isSelected && !isCorrect;

  Row({ space: 12 }) {
    // 选项标识
    Text(option.id)
      .fontSize(14)
      .fontWeight(FontWeight.Bold)
      .fontColor(isCorrect ? Color.White :
        (isWrong ? Color.White : $r('app.color.text_primary')))
      .width(28)
      .height(28)
      .textAlign(TextAlign.Center)
      .borderRadius(14)
      .backgroundColor(isCorrect ? '#4CAF50' :
        (isWrong ? '#F44336' : $r('app.color.background')))

    // 选项内容
    Text(option.content)
      .fontSize(14)
      .fontColor($r('app.color.text_primary'))
      .layoutWeight(1)

    // 状态标识
    if (isCorrect) {
      Text('✓ 正确答案')
        .fontSize(12)
        .fontColor('#4CAF50')
    } else if (isWrong) {
      Text('✗ 你的选择')
        .fontSize(12)
        .fontColor('#F44336')
    }
  }
  .width('100%')
  .padding(8)
  .backgroundColor(isCorrect ? '#E8F5E9' :
    (isWrong ? '#FFEBEE' : Color.Transparent))
  .borderRadius(6)
}

三、历史记录管理

3.1 历史记录页面

文件位置entry/src/main/ets/pages/Exam/ExamHistoryPage.ets

typescript 复制代码
import { router } from '@kit.ArkUI';
import { ExamLevel, ExamRecord } from '../../models/ExamModels';
import { ExamService } from '../../services/ExamService';

@Entry
@Component
export struct ExamHistoryPage {
  @State records: ExamRecord[] = [];
  @State level: ExamLevel = ExamLevel.BEGINNER;
  @State isLoading: boolean = true;

  private examService = ExamService.getInstance();

  async aboutToAppear(): Promise<void> {
    const params = router.getParams() as Record<string, Object>;
    this.level = params['level'] as ExamLevel;
    await this.loadRecords();
  }

  async loadRecords(): Promise<void> {
    this.isLoading = true;
    try {
      this.records = await this.examService.getExamRecordsByLevel(this.level);
      // 按时间倒序排列
      this.records.sort((a, b) => b.startTime - a.startTime);
    } catch (error) {
      console.error('[ExamHistoryPage] Failed to load records:', error);
    } finally {
      this.isLoading = false;
    }
  }

  build() {
    Column() {
      this.buildHeader()

      if (this.isLoading) {
        this.buildLoadingView()
      } else if (this.records.length === 0) {
        this.buildEmptyView()
      } else {
        this.buildRecordList()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background'))
  }
}

3.2 记录列表展示

typescript 复制代码
@Builder
buildHeader() {
  Row() {
    Text('< 返回')
      .fontSize(16)
      .fontColor($r('app.color.primary_professional'))
      .onClick(() => {
        router.back();
      })

    Blank()

    Text(this.getLevelName() + '历史记录')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)

    Blank()

    Text('   ')
  }
  .width('100%')
  .height(56)
  .padding({ left: 16, right: 16 })
  .backgroundColor($r('app.color.card_background'))
}

@Builder
buildRecordList() {
  Scroll() {
    Column({ space: 12 }) {
      ForEach(this.records, (record: ExamRecord, index: number) => {
        this.buildRecordCard(record, index + 1)
      })
    }
    .padding(16)
  }
  .layoutWeight(1)
}

@Builder
buildRecordCard(record: ExamRecord, index: number) {
  Column({ space: 12 }) {
    // 头部信息
    Row() {
      Column({ space: 4 }) {
        Text(`第${index}次考试`)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))

        Text(this.formatDate(record.startTime))
          .fontSize(12)
          .fontColor($r('app.color.text_tertiary'))
      }
      .alignItems(HorizontalAlign.Start)

      Blank()

      // 通过状态
      Text(record.passed ? '✓ 通过' : '✗ 未通过')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor(record.passed ? '#4CAF50' : '#F44336')
        .padding({ left: 12, right: 12, top: 6, bottom: 6 })
        .backgroundColor(record.passed ? '#E8F5E9' : '#FFEBEE')
        .borderRadius(12)
    }
    .width('100%')

    // 成绩信息
    Row({ space: 24 }) {
      Column({ space: 4 }) {
        Text(`${record.score}`)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor(record.passed ? '#4CAF50' : '#F44336')
        Text('得分')
          .fontSize(12)
          .fontColor($r('app.color.text_tertiary'))
      }

      Column({ space: 4 }) {
        Text(`${record.correctCount}/${record.totalQuestions}`)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))
        Text('正确率')
          .fontSize(12)
          .fontColor($r('app.color.text_tertiary'))
      }

      Column({ space: 4 }) {
        Text(this.formatDuration(record.endTime - record.startTime))
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor($r('app.color.text_primary'))
        Text('用时')
          .fontSize(12)
          .fontColor($r('app.color.text_tertiary'))
      }
    }
    .width('100%')

    // 操作按钮
    Row({ space: 8 }) {
      Button('查看详情')
        .layoutWeight(1)
        .height(36)
        .fontSize(14)
        .backgroundColor($r('app.color.card_background'))
        .fontColor($r('app.color.text_primary'))
        .onClick(() => {
          this.viewRecordDetail(record);
        })

      if (!record.passed) {
        Button('重新考试')
          .layoutWeight(1)
          .height(36)
          .fontSize(14)
          .backgroundColor($r('app.color.primary_professional'))
          .onClick(() => {
            this.retakeExam();
          })
      }
    }
    .width('100%')
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
  .shadow({ radius: 4, color: $r('app.color.shadow_light'), offsetY: 2 })
}

/**
 * 获取级别名称
 */
private getLevelName(): string {
  switch (this.level) {
    case ExamLevel.BEGINNER:
      return '初级';
    case ExamLevel.INTERMEDIATE:
      return '中级';
    case ExamLevel.ADVANCED:
      return '高级';
    default:
      return '未知';
  }
}

/**
 * 格式化日期
 */
private formatDate(timestamp: number): string {
  const date = new Date(timestamp);
  return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}

/**
 * 查看记录详情
 */
private viewRecordDetail(record: ExamRecord): void {
  // TODO: 跳转到详情页面或显示详情对话框
  router.pushUrl({
    url: 'pages/Exam/ExamResultPage',
    params: {
      record: record,
      questions: [] // 需要重新获取题目数据
    }
  });
}

/**
 * 重新考试
 */
private retakeExam(): void {
  router.pushUrl({
    url: 'pages/Exam/ExamQuestionPage',
    params: { level: this.level }
  });
}

四、统计分析功能

4.1 ExamService 统计方法扩展

ExamService 中添加更多统计分析方法:

typescript 复制代码
/**
 * 获取考试统计数据
 */
public async getExamStats(level?: ExamLevel): Promise<ExamStats> {
  const records = level ?
    await this.getExamRecordsByLevel(level) :
    await this.getExamRecords();

  if (records.length === 0) {
    return {
      totalExams: 0,
      passedExams: 0,
      averageScore: 0,
      bestScore: 0,
      weakCategories: []
    };
  }

  const totalExams = records.length;
  const passedExams = records.filter(r => r.passed).length;
  const averageScore = Math.round(records.reduce((sum, r) => sum + r.score, 0) / totalExams);
  const bestScore = Math.max(...records.map(r => r.score));

  // 统计薄弱知识点
  const categoryErrors: Record<string, number> = {};
  records.forEach(record => {
    record.answers.forEach(answer => {
      if (!answer.isCorrect) {
        const question = this.getQuestionById(answer.questionId);
        if (question) {
          categoryErrors[question.category] = (categoryErrors[question.category] || 0) + 1;
        }
      }
    });
  });

  // 获取错误最多的3个知识点
  const weakCategories = Object.entries(categoryErrors)
    .sort(([,a], [,b]) => b - a)
    .slice(0, 3)
    .map(([category]) => category);

  return {
    totalExams,
    passedExams,
    averageScore,
    bestScore,
    weakCategories
  };
}

/**
 * 获取成绩趋势数据
 */
public async getScoreTrend(level: ExamLevel): Promise<number[]> {
  const records = await this.getExamRecordsByLevel(level);
  return records
    .sort((a, b) => a.startTime - b.startTime)
    .map(r => r.score);
}

/**
 * 获取错题统计
 */
public async getWrongQuestionStats(level: ExamLevel): Promise<Record<string, number>> {
  const records = await this.getExamRecordsByLevel(level);
  const categoryErrors: Record<string, number> = {};

  records.forEach(record => {
    record.answers.forEach(answer => {
      if (!answer.isCorrect) {
        const question = this.getQuestionById(answer.questionId);
        if (question) {
          categoryErrors[question.category] = (categoryErrors[question.category] || 0) + 1;
        }
      }
    });
  });

  return categoryErrors;
}

/**
 * 根据ID获取题目
 */
private getQuestionById(questionId: string): ExamQuestion | null {
  const allQuestions = this.getAllQuestions();
  return allQuestions.find(q => q.id === questionId) || null;
}

五、实操练习

练习1:添加成绩趋势图

任务:在历史记录页面添加成绩趋势图表

提示

  1. 使用 Canvas 或第三方图表库
  2. 显示最近10次考试的成绩变化
  3. 标注通过线和平均分

参考代码

typescript 复制代码
@Builder
buildScoreTrendChart() {
  Column({ space: 8 }) {
    Text('成绩趋势')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .width('100%')

    // 简化的趋势图(使用进度条模拟)
    Column({ space: 4 }) {
      ForEach(this.getRecentScores(), (score: number, index: number) => {
        Row() {
          Text(`第${index + 1}次`)
            .fontSize(12)
            .width(60)

          Progress({ value: score, total: 100, type: ProgressType.Linear })
            .width(200)
            .height(8)
            .color(score >= 60 ? '#4CAF50' : '#F44336')

          Text(`${score}分`)
            .fontSize(12)
            .width(50)
        }
      })
    }
  }
  .width('100%')
  .padding(16)
  .backgroundColor($r('app.color.card_background'))
  .borderRadius(12)
}

private getRecentScores(): number[] {
  return this.records.slice(0, 10).reverse().map(r => r.score);
}

练习2:添加错题本功能

任务:创建独立的错题本页面,收集所有错题

提示

  1. 从所有考试记录中提取错题
  2. 按知识点分类显示
  3. 支持错题重做功能

练习3:添加学习建议

任务:根据考试结果生成个性化学习建议

提示

  1. 分析薄弱知识点
  2. 推荐相关课程
  3. 制定学习计划

六、本篇总结

6.1 核心知识点

本篇教程完善了考试系统的分析功能,涵盖以下内容:

  1. 成绩展示

    • 成绩概览设计
    • 统计数据展示
    • 通过状态判断
  2. 错题分析

    • 错题列表展示
    • 答案解析功能
    • 薄弱环节分析
  3. 历史记录

    • 记录列表管理
    • 成绩趋势分析
    • 重考功能
  4. 数据统计

    • 考试统计计算
    • 知识点分析
    • 错题统计

6.2 技术要点

  • 条件渲染:根据状态切换不同视图
  • 数据分析:统计计算和趋势分析
  • 列表展示:ForEach 渲染记录列表
  • 状态标识:不同颜色表示不同状态
  • 交互设计:查看详情、重新考试等操作

6.3 实际应用价值

考试分析系统解决了以下问题:

  1. 学习效果评估:通过成绩了解学习效果
  2. 薄弱环节识别:找出需要加强的知识点
  3. 学习进度跟踪:记录学习历程和进步
  4. 针对性学习:根据错题进行针对性复习
  5. 学习动机激励:成绩展示和进步可视化

6.4 下一篇预告

第28篇:用户中心与个人资料

下一篇将实现用户中心功能,包括:

  • 👤 个人资料管理
  • ⚙️ 账号设置功能
  • 📊 学习统计展示
  • 🏆 成就系统
  • 📱 应用设置

附录:完整代码文件清单

页面层

  • entry/src/main/ets/pages/Exam/ExamResultPage.ets - 成绩结果页面
  • entry/src/main/ets/pages/Exam/ExamHistoryPage.ets - 历史记录页面

服务层

  • entry/src/main/ets/services/ExamService.ets - 考试服务(新增统计方法)
相关推荐
菜鸟小芯2 小时前
【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&我的页面功能实现
flutter·harmonyos
灰灰勇闯IT2 小时前
Flutter for OpenHarmony:悬浮按钮(FloatingActionButton)最佳实践 —— 强化核心操作,提升用户效率
flutter·华为·交互
一起养小猫3 小时前
Flutter for OpenHarmony 进阶:表达式解析算法与计算器核心实现
算法·flutter·harmonyos
听麟4 小时前
HarmonyOS 6.0+ PC端系统级桌面插件开发实战:ArkUI Widget进阶与系统交互深度集成
华为·交互·harmonyos
不爱吃糖的程序媛4 小时前
Flutter 三方库鸿蒙(OHOS)适配分析流程
flutter·华为·harmonyos
2301_796512524 小时前
【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Lazyload 懒加载(懒加载的图片)
前端·javascript·react native·react.js·ecmascript·harmonyos
mocoding4 小时前
我这样用鸿蒙化Flutter三方库file_selector实现单图片和多图片选择
flutter·华为·harmonyos
听麟5 小时前
HarmonyOS 6.0+ PC端视频剪辑工具开发实战:Media Kit进阶与硬件加速渲染落地
华为·harmonyos
浩宇软件开发5 小时前
基于OpenHarmony鸿蒙开发医院预约挂号系统(前端后端分离)
前端·华为·harmonyos