【高心星出品】
项目按照从数据库连接层–视图层–业务逻辑层这种三层架构开发,所以先设计了数据库表格的结构,在EntryAbility中创建表格。
欢迎页面效果
数据字典
- searchmodel的数据字典:
/***
** 搜索历史记录模型*
** 用于存储用户搜索关键词的历史记录*
**/*
export interface SearchHistoryItem {
id?: number *// 记录ID,可选*
keyword: string *// 搜索关键词*
timestamp?: string *// 搜索时间戳,可选*
}
- adcode的数据字典:
/***
** 城市编码模型*
** 用于存储城市相关的编码信息*
**/*
export interface adcode {
id: number *// 城市ID*
pid?: number *// 父级ID,可选*
city_code: string *// 城市编码*
city_name: string *// 城市名称*
post_code?: string *// 邮政编码,可选*
area_code?: string *// 区号,可选*
ctime?: string *// 创建时间,可选*
}
这两个模型的主要用途:
SearchHistoryItem:
用于记录用户的搜索历史
包含搜索关键词和时间信息
可用于实现搜索历史功能
adcode:
用于存储城市信息
包含城市编码、名称等基本信息
支持城市层级关系(通过pid)
包含邮政和区号等附加信息
创建数据库表格
编写DbUtils实现对于表格的创建。
创建名为 weatherinfo.db 的数据库
设置数据库安全级别为 S1
初始化两个数据表:
t_adcode:存储城市信息
t_search:存储搜索历史
import { relationalStore } from "@kit.ArkData";
/**
*作者:gxx
*时间:2025/4/21 9:16
*功能:数据库操作
**/
// 城市编码表名
export const TABLENAME: string = 't_adcode'
// 搜索历史表名
export const TABLENAME1: string = 't_search'
// 数据库配置
const CONFIG: relationalStore.StoreConfig = {
name: 'weatherinfo.db', // 数据库名称
securityLevel: relationalStore.SecurityLevel.S1 // 安全级别
}
/**
* 获取数据库实例
* @param context 上下文对象
* @returns 数据库实例
*/
export async function getrdb(context: Context) {
return relationalStore.getRdbStore(context, CONFIG)
}
/**
* 创建数据库表
* @param context 上下文对象
* @description 创建城市编码表和搜索历史表
*/
export async function createtable(context: Context) {
try {
// 创建城市编码表SQL语句
let sql = 'CREATE TABLE IF NOT EXISTS t_adcode (\n' +
' id INTEGER PRIMARY KEY AUTOINCREMENT,\n' + // 自增主键
'name TEXT NOT NULL, code VARCHAR(50) NOT NULL,\n' + // 城市名称和编码
' city VARCHAR(50) \n' + // 所属城市
');'
// 创建搜索历史表SQL语句
let sql1 = 'CREATE TABLE IF NOT EXISTS t_search (\n' +
' id INTEGER PRIMARY KEY AUTOINCREMENT,\n' + // 自增主键
'keyword TEXT NOT NULL, timestamp VARCHAR(50) NOT NULL);' // 搜索关键词和时间戳
// 获取数据库实例并执行SQL
let rdb = await getrdb(context)
rdb.executeSync(sql)
rdb.executeSync(sql1)
} catch (e) {
console.error('dbutils 创建表格失败: ', JSON.stringify(e))
}
}
在EntryAbility中创建表格。
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { createtable } from '../utils/Dbutils';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
// 1. 记录窗口创建日志
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
// 2. 创建数据库表格
// 创建adcode和search_history两个表格用于存储城市代码和搜索历史
await createtable(this.context)
// 3. 加载启动页面
// 加载splash页面,splash页面会检查是否已导入城市数据
// 如果未导入则会从cityma.txt读取并导入数据库
// 导入完成后跳转到主页面Index
windowStage.loadContent('pages/splash', async (err) => {
if (err.code) {
// 4. 加载失败处理
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
// 5. 加载成功记录
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
}
Splash页面
页面功能
1.数据初始化:
页面启动时会检查是否已经加载过城市数据(通过 PreferenceUtils 存储的 isload 标志)
如果数据未加载,会执行 charu 函数来初始化数据
2.charu 函数的具体工作:
从资源文件中读取 cityma.txt(城市码数据文件)
将文件内容解析为 JSON 数据
通过 adcodedao 将城市数据插入到数据库中
使用 @Concurrent 注解确保在后台线程执行,避免阻塞主线程
3.界面展示:
显示一个启动图片
展示一个环形进度条动画
显示"加载中…"文字
4.页面跳转:
- 数据加载完成后,会自动跳转到主页面(pages/Index)
欢迎页代码
import { adcodedao } from '../dao/adcodedao';
import { buffer, taskpool } from '@kit.ArkTS';
import { common } from '@kit.AbilityKit';
import { adcode } from '../model/adcode';
import { PreferenceUtils } from '../utils/PreferenceUtils';
import { router } from '@kit.ArkUI';
/**
* 插入城市数据到数据库
* @param context 上下文对象
* @returns 插入是否成功
*/
@Concurrent
async function charu(context: Context) {
try {
// 创建数据访问对象
let ad = new adcodedao(context)
// 读取原始文件内容
let value = await context.resourceManager.getRawFileContent('cityma.txt')
// 转换为字符串
let data = buffer.from(value).toString()
// 解析JSON数据
let content = JSON.parse(data) as adcode[]
// 插入数据库
await ad.insert(content)
return true
} catch (e) {
console.error('gxxt ', JSON.stringify(e))
return false
}
}
// 持久化存储的key名称
const Prename: string = 'loaded'
/**
* 启动页面组件
*/
@Entry
@Component
struct Splash {
@State message: string = 'Hello World';
// 进度条值
@State value: number = 10
// 上下文对象
private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
// 定时器ID
private intervalid: number = 0
/**
* 组件即将出现时的生命周期函数
*/
aboutToAppear(): void {
// 设置定时器更新进度条
this.intervalid = setInterval(() => {
if (this.value == 100) {
this.value = -10
}
this.value += 10
}, 100)
// 检查是否已加载过数据
let loaded = PreferenceUtils.getInstance(this.context, Prename).get('isload', false) as boolean
if (loaded) {
// 如果已加载过数据,直接跳转到主页
router.replaceUrl({ url: 'pages/Index' })
} else {
// 如果未加载过数据,则读取文件并插入数据库
taskpool.execute(charu, this.context).then((value) => {
// 保存加载状态
PreferenceUtils.getInstance(this.context, Prename).putfile('isload', value as boolean)
// 跳转到主页
router.replaceUrl({ url: 'pages/Index' })
})
}
}
/**
* 构建UI界面
*/
build() {
Stack() {
// 背景图片
Image($r('app.media.splash'))
// 进度条
Progress({ value: this.value, total: 100, type: ProgressType.ScaleRing })
.width(100)
.height(100)
.backgroundColor(Color.Black)
.style({ strokeWidth: 15, scaleCount: 20, scaleWidth: 5 })
.color(Color.White)
// 加载提示文本
Text('加载中...').fontWeight(FontWeight.Bolder)
}
.height('100%')
.width('100%')
.alignContent(Alignment.Center)
}
/**
* 组件即将消失时的生命周期函数
*/
aboutToDisappear(): void {
// 清除定时器
clearInterval(this.intervalid)
}
}
亮点
1.使用多线程将大量数据批量插入数据库
批量插入:
/**
* 批量插入城市数据到数据库
* @param acs 城市数据数组
* @description 将城市数据批量插入到数据库中
* 如果城市数据包含ctime字段,则插入name、code、city三个字段
* 否则只插入name和code两个字段
*/
async insert(acs: adcode[]) {
try {
// 获取数据库实例
let rdb = await getrdb(this.context)
// 存储要插入的数据
let values: relationalStore.ValuesBucket[] = []
// 遍历城市数据
acs.forEach((ac: adcode) => {
let value: relationalStore.ValuesBucket
// 判断是否有ctime字段
if (ac.ctime) {
value = {
'name': ac.city_name, // 城市名称
'code': ac.city_code, // 城市编码
'city': ac.ctime // 所属城市
}
} else {
value = {
'name': ac.city_name, // 城市名称
'code': ac.city_code, // 城市编码
}
}
values.push(value)
})
// 批量插入数据
rdb.batchInsertSync(TABLENAME, values)
} catch (e) {
console.error('gxxt adcodedao 插入数据错误: ', e.message)
}
}
多线程运行实体:
@Concurrent
async function charu(context: Context) {
try {
// 创建数据访问对象
let ad = new adcodedao(context)
// 读取原始文件内容
let value = await context.resourceManager.getRawFileContent('cityma.txt')
// 转换为字符串
let data = buffer.from(value).toString()
// 解析JSON数据
let content = JSON.parse(data) as adcode[]
// 插入数据库
await ad.insert(content)
return true
} catch (e) {
console.error('gxxt ', JSON.stringify(e))
return false
}
}
多线程执行:
// 如果未加载过数据,则读取文件并插入数据库
taskpool.execute(charu, this.context).then((value) => {
// 保存加载状态
PreferenceUtils.getInstance(this.context, Prename).putfile('isload', value as boolean)
// 跳转到主页
router.replaceUrl({ url: 'pages/Index' })
})
2.使用Interval配合环形刻度进度条来制作动态的加载动画
// 设置定时器更新进度条
this.intervalid = setInterval(() => {
if (this.value == 100) {
this.value = -10
}
this.value += 10
}, 100)
....................................
// 进度条
Progress({ value: this.value, total: 100, type: ProgressType.ScaleRing })
.width(100)
.height(100)
.backgroundColor(Color.Black)
.style({ strokeWidth: 15, scaleCount: 20, scaleWidth: 5 })
.color(Color.White)