一、结果页面概览

测评完成后,用户进入 ResultPage 查看结果。该页面需要完成三件事:

  1. 根据量表类型计算评级与颜色请添加图片描述

  2. 展示分数、说明与提示

  3. 将分数存入本地数据库

二、结果页面实现

2.1 页面结构与参数

export interface ResultParams {
  score?: number
  firstScore?: number
  secondScore?: number
  thirdScore?: number
  forthScore?: number
  oddScore?: number
  evenScore?: number
  type?: number
  title?: string
  url?: string
}

@ComponentV2
export struct ResultPage {
  private db = GetIBestORM();
  @Local title: string = "柚兔自测量表"
  @Local type: number = FormType.TYPE_ABC
  @Local score: number = 0
  @Local finalType: string = ""
  @Local strExplain: string = ""
  @Local strTips: string = ""
  @Local tipsVisible: boolean = false
  @Local scoreColor: Resource = $r('app.color.color_default')
  private pageContext: PageContext = AppStorage.get('pageContext') as PageContext;

  build() {
    NavDestination() {
      Column({ space: 12 }) {
        this.buildTopBar()
        this.buildContent()
      }
      .backgroundColor($r('app.color.color_background'))
    }.hideTitleBar(true)
    .onReady((ctx: NavDestinationContext) => {
      this.initResult(ctx);
    })
  }
}

2.2 结果初始化逻辑

onReady 回调在 NavDestination 准备就绪时触发,此时可获取路由参数:

private initResult(ctx: NavDestinationContext) {
  const params = ctx.pathInfo.param as ResultParams;
  this.title = params.title as string;
  this.score = params.score as number;
  this.type = params.type as number;

  // MBTI 类型计算
  this.finalType = this.firstScore > 3 ? "E" : "I";
  this.finalType += this.secondScore > 3 ? "N" : "S";
  this.finalType += this.thirdScore > 3 ? "F" : "T";
  this.finalType += this.forthScore > 3 ? "J" : "P";

  // 根据量表类型设置评级标准
  switch (this.type) {
    case FormType.TYPE_ABC:
      this.scoreFirst = 31;
      this.scoreSecond = 53;
      this.strExplain = '总分≥31分为自闭症筛查界限分;总分>53分作为自闭症诊断界限分';
      this.strTips = '结果仅供参考,如果测试分数过高请去医院筛查。';
      if (this.score < 31) {
        this.scoreColor = $r('app.color.color_result_1');  // 绿色-正常
      } else if (this.score >= 31 && this.score < 53) {
        this.scoreColor = $r('app.color.color_result_2');  // 黄色-轻度
      } else {
        this.scoreColor = $r('app.color.color_result_4');  // 红色-严重
        this.tipsVisible = true;
      }
      break;
    
    case FormType.TYPE_SDS:
      this.scoreFirst = 53;
      this.scoreSecond = 63;
      this.scoreThird = 73;
      this.strExplain = '标准分低于53分正常,53-62轻度抑郁,63-72中度,73以上重度';
      if (this.score < 53) {
        this.scoreColor = $r('app.color.color_result_1');
      } else if (this.score >= 53 && this.score < 63) {
        this.scoreColor = $r('app.color.color_result_2');
        this.tipsVisible = true;
      } else if (this.score >= 63 && this.score < 73) {
        this.scoreColor = $r('app.color.color_result_3');
        this.tipsVisible = true;
      } else {
        this.scoreColor = $r('app.color.color_result_4');
        this.tipsVisible = true;
      }
      break;
    // ... 其他量表类型
  }

  this.updateLastScore(this.type, this.score);
}

关键设计:通过 scoreColor 动态设置颜色,绿色表示正常、黄色表示轻度、橙色表示中度、红色表示重度,视觉反馈直观。

2.3 结果展示 UI

@Builder
buildContent() {
  Column({ space: 15 }) {
    // 分数卡片
    Column({ space: 8 }) {
      Text(this.type === FormType.TYPE_MBTI ? '测试者的性格典型' : '分数')
        .fontSize(18)
        .fontColor($r('app.color.color_title'))
        .fontWeight(FontWeight.Bold)
      Text(this.type === FormType.TYPE_MBTI ? `${this.finalType}` : this.score.toString())
        .fontSize(20)
        .fontColor(this.scoreColor)
        .fontWeight(FontWeight.Bold)
    }.backgroundColor($r('app.color.color_card')).width('95%').borderRadius(12).padding(15)

    // 说明卡片
    Column({ space: 8 }) {
      Text(this.type === FormType.TYPE_MBTI ? '适合职业' : '结果说明')
        .fontSize(18)
        .fontColor($r('app.color.color_title'))
        .fontWeight(FontWeight.Bold)
      Text(this.strExplain)
        .fontColor(this.scoreColor)
        .fontWeight(FontWeight.Bold)
    }.backgroundColor($r('app.color.color_card')).width('95%').borderRadius(12).padding(15)

    // 提示卡片(条件显示)
    Column({ space: 8 }) {
      Text('提示').fontSize(18).fontColor($r('app.color.color_title')).fontWeight(FontWeight.Bold)
      Text(this.strTips).fontSize(12)
    }
    .backgroundColor($r('app.color.color_card'))
    .width('95%')
    .borderRadius(12)
    .padding(15)
    .visibility(this.tipsVisible ? Visibility.Visible : Visibility.None)
  }.width('100%').layoutWeight(1)
}

三、IBest-ORM 本地数据持久化

3.1 ORM 模型定义

MeCharts 使用 @ibestservices/ibest-orm 进行本地数据存储,通过装饰器定义数据表:

最新分数表(Last)

import { Field, FieldType, Model, Table } from "@ibestservices/ibest-orm";

@Table
export class Last extends Model {
  @Field({ type: FieldType.TEXT })
  score?: string

  @Field({ type: FieldType.INTEGER })
  type?: number

  constructor(score: string, type: number) {
    super();
    this.score = score;
    this.type = type;
  }
}

历史记录表(History)

@Table
export class History extends Model {
  @Field({ type: FieldType.TEXT })
  score?: string

  @Field({ type: FieldType.TEXT })
  time?: string

  @Field({ type: FieldType.INTEGER })
  type?: number

  constructor(score: string, time: string, type: number) {
    super();
    this.score = score;
    this.time = time;
    this.type = type;
  }
}

装饰器说明:

  • @Table:标记类为数据表,自动映射为 SQLite 表
  • @Field:标记字段为表列,支持 TEXTINTEGERREAL 等类型
  • extends Model:继承基类,获得 CRUD 方法

3.2 写入数据

测评完成后,更新或插入最新分数,并添加历史记录:

async updateLastScore(type: number, score: number) {
  this.db.AutoMigrate(Last);
  this.db.AutoMigrate(History);

  // 查询是否已存在该类型
  let result = this.db.Table("last").Where('type', type).Find();
  
  if (result.length > 0) {
    // 已存在则更新
    this.db.Table("last").Where('type', type).Update({ score: `${score}` });
  } else {
    // 不存在则创建
    const lastScore = new Last(`${score}`, type);
    await this.db.Create(lastScore);
  }

  // 添加历史记录
  const history = new History(`${score}`, DateUtils.getCurrentTime(), type);
  this.db.Create(history);
}

核心 API:

方法 说明
AutoMigrate(model) 自动迁移表结构(新增表或字段)
Table(name) 获取表操作对象
Where(field, value) 设置查询条件
Find() 查询所有匹配记录
Create(entity) 插入一条记录
Update(data) 更新匹配记录

3.3 读取数据

历史记录页面读取特定量表的历史记录:

this.results = this.db.Table("history").Where('type', this.type).Find();

首页读取最新分数:

this.result = this.db.Table("last").Find();

3.4 DateUtils 时间工具

export class DateUtils {
  static getCurrentTime(format?: string): string {
    return DateUtils.formatDate(Date.now(), format)
  }

  static formatDate(timestamp: number, format: string = 'yyyy-MM-dd HH:mm:ss'): string {
    const date = new Date(timestamp)
    return format
      .replace('yyyy', String(date.getFullYear()))
      .replace('MM', String(date.getMonth() + 1).padStart(2, '0'))
      .replace('dd', String(date.getDate()).padStart(2, '0'))
      .replace('HH', String(date.getHours()).padStart(2, '0'))
      .replace('mm', String(date.getMinutes()).padStart(2, '0'))
      .replace('ss', String(date.getSeconds()).padStart(2, '0'))
  }
}

四、历史记录页面

4.1 RecordPage / RecordView

历史记录页面展示所有量表的最新分数,支持下拉刷新:

@Component
export struct RecordPage {
  @State scoreList: AbcModel[] = [];
  private db = GetIBestORM();
  @State isRefreshing: boolean = false;

  private reload() {
    this.db = GetIBestORM();
    this.db.AutoMigrate(Last);
    this.db.AutoMigrate(History);
    this.result = this.db.Table("last").Find();

    if (this.result.length > 0) {
      this.result.forEach((item: Last) => {
        const existingIndex = this.scoreList.findIndex(scoreItem => scoreItem.type === item.type);
        
        if (item.type === FormType.TYPE_ABC) {
          if (existingIndex !== -1) {
            this.scoreList[existingIndex].score = item.score;
          } else {
            let newData = new AbcModel($r('app.media.ic_item_abc'), 'ABC量表', '(自闭症行为评定量表)', 1,
              item.score, item.type);
            this.scoreList.push(newData);
          }
        }
        // ... 其他量表类型
      });
    }
  }

  build() {
    NavDestination() {
      Column() {
        this.buildTopBar();
        Refresh({ refreshing: $$this.isRefreshing }) {
          if (this.scoreList.length === 0) {
            this.buildEmptyView()
          } else {
            this.buildContentView()
          }
        }.onRefreshing(() => {
          this.isRefreshing = true
          setTimeout(() => {
            this.reload();
            this.isRefreshing = false;
          }, 1000)
        })
        .layoutWeight(1)
        .refreshOffset(64)
        .pullToRefresh(true)
      }
    }
  }
}

4.2 Refresh 下拉刷新

Refresh({ refreshing: $$this.isRefreshing }) {
  // 内容
}.onRefreshing(() => {
  this.isRefreshing = true
  setTimeout(() => {
    this.reload();
    this.isRefreshing = false;
  }, 1000)
})
.refreshOffset(64)      // 触发刷新的下拉距离
.pullToRefresh(true)    // 启用下拉刷新

$$this.isRefreshing 使用双向绑定语法,Refresh 组件会自动控制 isRefreshing 的值来显示/隐藏刷新动画。

4.3 HistoryPage 历史详情

点击某个量表条目后,进入历史详情页查看该量表的所有测评记录:

@ComponentV2
export struct HistoryPage {
  @Local results: Array<Record<string, relationalStore.ValueType>> = []
  private db = GetIBestORM();

  build() {
    NavDestination() {
      Column({ space: 12 }) {
        this.buildTopBar()
        if (this.results.length === 0) {
          this.buildEmpty()
        } else {
          this.buildContent()
        }
      }
    }.onReady((ctx: NavDestinationContext) => {
      const params = ctx.pathInfo.param as ResultParams;
      this.title = params.title as string
      this.type = params.type as number
      this.results = this.db.Table("history").Where('type', this.type).Find();
    })
  }

  @Builder
  buildContent() {
    List({ space: 20 }) {
      ForEach(this.results.reverse(), (item: History, index) => {
        ListItem() {
          Row({ space: 7 }) {
            Text((index + 1).toString());
            Text(item.score);
            Text(item.time).fontSize(12).fontColor($r('app.color.color_text'))
          }
          .width('100%')
          .padding(15)
          .backgroundColor($r('app.color.color_card'))
          .borderRadius(12)
        }
      })
    }
  }
}

this.results.reverse() 将记录倒序显示,最新的测评记录排在最前面。

五、ORM 与原生 RdbStore 的对比

MeCharts 中同时存在两套数据库方案:

特性 IBest-ORM 原生 RdbStore
代码风格 装饰器 + 链式调用 SQL 语句 + 回调
表创建 @Table 自动迁移 手动 executeSql
CRUD Create/Find/Update insert/query/update
适用场景 快速开发 复杂查询

IBest-ORM 适合快速开发,代码简洁;原生 RdbStore 适合需要精细控制 SQL 的场景。MeCharts 中 IBest-ORM 负责量表数据,原生 RdbStore 用于云备份相关功能。

六、小结

本篇讲解了测评结果页面的评级逻辑与 UI 展示,以及基于 IBest-ORM 的本地数据持久化方案。通过装饰器驱动的 ORM 模型,数据层代码极为简洁。下一篇将深入网络请求封装与 AI 咨询功能的实现。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐