鸿蒙5.0项目开发——鸿蒙天气项目的实现(欢迎页)

发布于:2025-05-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

【高心星出品】

项目按照从数据库连接层–视图层–业务逻辑层这种三层架构开发,所以先设计了数据库表格的结构,在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)