HarmonyOS 实战教程(六):测评结果页面与本地数据持久化 —— 以「柚兔自测量表」为例
·
一、结果页面概览
测评完成后,用户进入 ResultPage 查看结果。该页面需要完成三件事:
-
根据量表类型计算评级与颜色

-
展示分数、说明与提示
-
将分数存入本地数据库
二、结果页面实现
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:标记字段为表列,支持TEXT、INTEGER、REAL等类型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 咨询功能的实现。
更多推荐


所有评论(0)