
鸿蒙跨设备单词本应用设计与实现 原创
鸿蒙跨设备单词本应用设计与实现
一、系统架构设计
基于HarmonyOS的关系型数据库和分布式能力,我们设计了一个跨设备同步的单词本应用,能够存储生词本并在多设备间同步。
!https://example.com/vocabulary-app-arch.png
系统包含四个核心模块:
数据库模块 - 使用@ohos.data.relationalStore存储单词数据
分布式同步模块 - 通过@ohos.distributedData实现多设备数据同步
UI组件模块 - 提供单词列表展示和操作界面
搜索模块 - 实现单词的本地搜索功能
二、核心代码实现
数据库服务(ArkTS)
// VocabularyDB.ets
import relationalStore from ‘@ohos.data.relationalStore’;
const DB_NAME = ‘vocabulary.db’;
const TABLE_NAME = ‘words’;
const DB_VERSION = 1;
class VocabularyDB {
private static instance: VocabularyDB = null;
private rdbStore: relationalStore.RdbStore;
private constructor() {
this.initDB();
public static getInstance(): VocabularyDB {
if (!VocabularyDB.instance) {
VocabularyDB.instance = new VocabularyDB();
return VocabularyDB.instance;
private async initDB() {
const config: relationalStore.StoreConfig = {
name: DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: false
};
try {
this.rdbStore = await relationalStore.getRdbStore(this.getContext(), config);
await this.createTable();
console.info('数据库初始化成功');
catch (err) {
console.error('数据库初始化失败:', JSON.stringify(err));
}
private async createTable() {
const sql = CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
word TEXT NOT NULL,
translation TEXT,
example TEXT,
create_time INTEGER,
update_time INTEGER,
device_id TEXT
);
await this.rdbStore.executeSql(sql);
private getContext() {
// 获取应用上下文
return getContext(this) as Context;
public async addWord(word: Word): Promise<number> {
const valueBucket: relationalStore.ValuesBucket = {
'word': word.word,
'translation': word.translation,
'example': word.example,
'create_time': Date.now(),
'update_time': Date.now(),
'device_id': this.getDeviceId()
};
try {
const rowId = await this.rdbStore.insert(TABLE_NAME, valueBucket);
return rowId;
catch (err) {
console.error('添加单词失败:', JSON.stringify(err));
return -1;
}
public async updateWord(word: Word): Promise<boolean> {
const valueBucket: relationalStore.ValuesBucket = {
‘word’: word.word,
‘translation’: word.translation,
‘example’: word.example,
‘update_time’: Date.now(),
‘device_id’: this.getDeviceId()
};
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', word.id);
try {
const rowsUpdated = await this.rdbStore.update(valueBucket, predicates);
return rowsUpdated > 0;
catch (err) {
console.error('更新单词失败:', JSON.stringify(err));
return false;
}
public async deleteWord(id: number): Promise<boolean> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo(‘id’, id);
try {
const rowsDeleted = await this.rdbStore.delete(predicates);
return rowsDeleted > 0;
catch (err) {
console.error('删除单词失败:', JSON.stringify(err));
return false;
}
public async getAllWords(): Promise<Word[]> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.orderByDesc(‘update_time’);
try {
const resultSet = await this.rdbStore.query(predicates, [
'id', 'word', 'translation', 'example', 'create_time', 'update_time', 'device_id'
]);
const words: Word[] = [];
while (resultSet.goToNextRow()) {
words.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
word: resultSet.getString(resultSet.getColumnIndex('word')),
translation: resultSet.getString(resultSet.getColumnIndex('translation')),
example: resultSet.getString(resultSet.getColumnIndex('example')),
createTime: resultSet.getLong(resultSet.getColumnIndex('create_time')),
updateTime: resultSet.getLong(resultSet.getColumnIndex('update_time')),
deviceId: resultSet.getString(resultSet.getColumnIndex('device_id'))
});
resultSet.close();
return words;
catch (err) {
console.error('查询单词失败:', JSON.stringify(err));
return [];
}
public async searchWords(keyword: string): Promise<Word[]> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.contains(‘word’, keyword);
predicates.or().contains(‘translation’, keyword);
predicates.orderByDesc(‘update_time’);
try {
const resultSet = await this.rdbStore.query(predicates, [
'id', 'word', 'translation', 'example', 'create_time', 'update_time', 'device_id'
]);
const words: Word[] = [];
while (resultSet.goToNextRow()) {
words.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
word: resultSet.getString(resultSet.getColumnIndex('word')),
translation: resultSet.getString(resultSet.getColumnIndex('translation')),
example: resultSet.getString(resultSet.getColumnIndex('example')),
createTime: resultSet.getLong(resultSet.getColumnIndex('create_time')),
updateTime: resultSet.getLong(resultSet.getColumnIndex('update_time')),
deviceId: resultSet.getString(resultSet.getColumnIndex('device_id'))
});
resultSet.close();
return words;
catch (err) {
console.error('搜索单词失败:', JSON.stringify(err));
return [];
}
private getDeviceId(): string {
// 实际实现需要获取设备ID
return ‘local_device’;
}
interface Word {
id?: number;
word: string;
translation?: string;
example?: string;
createTime?: number;
updateTime?: number;
deviceId?: string;
export const vocabularyDB = VocabularyDB.getInstance();
分布式同步服务(ArkTS)
// VocabularySync.ets
import distributedData from ‘@ohos.distributedData’;
const SYNC_CHANNEL = ‘vocabulary_sync’;
class VocabularySync {
private static instance: VocabularySync = null;
private dataManager: distributedData.DataManager;
private listeners: SyncListener[] = [];
private constructor() {
this.initDataManager();
public static getInstance(): VocabularySync {
if (!VocabularySync.instance) {
VocabularySync.instance = new VocabularySync();
return VocabularySync.instance;
private initDataManager() {
this.dataManager = distributedData.createDataManager({
bundleName: 'com.example.vocabulary',
area: distributedData.Area.GLOBAL
});
this.dataManager.registerDataListener(SYNC_CHANNEL, (data) => {
this.handleSyncData(data);
});
public async syncWord(word: Word, action: ‘add’ ‘update’
‘delete’): Promise<void> {
this.dataManager.syncData(SYNC_CHANNEL, {
type: action,
word: word,
timestamp: Date.now(),
deviceId: this.getDeviceId()
});
public async syncAllWords(): Promise<void> {
const words = await vocabularyDB.getAllWords();
this.dataManager.syncData(SYNC_CHANNEL, {
type: 'fullSync',
words: words,
timestamp: Date.now(),
deviceId: this.getDeviceId()
});
public addListener(listener: SyncListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
public removeListener(listener: SyncListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
private handleSyncData(data: any): void {
if (!data) return;
switch (data.type) {
case 'add':
this.handleAddWord(data.word, data.deviceId);
break;
case 'update':
this.handleUpdateWord(data.word, data.deviceId);
break;
case 'delete':
this.handleDeleteWord(data.word, data.deviceId);
break;
case 'fullSync':
this.handleFullSync(data.words, data.deviceId);
break;
}
private async handleAddWord(word: Word, deviceId: string) {
// 忽略自己设备同步的数据
if (deviceId === this.getDeviceId()) return;
try {
await vocabularyDB.addWord(word);
this.notifyListeners('add', word);
catch (err) {
console.error('同步添加单词失败:', JSON.stringify(err));
}
private async handleUpdateWord(word: Word, deviceId: string) {
if (deviceId === this.getDeviceId()) return;
try {
const success = await vocabularyDB.updateWord(word);
if (success) {
this.notifyListeners('update', word);
} catch (err) {
console.error('同步更新单词失败:', JSON.stringify(err));
}
private async handleDeleteWord(word: Word, deviceId: string) {
if (deviceId === this.getDeviceId()) return;
try {
const success = await vocabularyDB.deleteWord(word.id!);
if (success) {
this.notifyListeners('delete', word);
} catch (err) {
console.error('同步删除单词失败:', JSON.stringify(err));
}
private async handleFullSync(words: Word[], deviceId: string) {
if (deviceId === this.getDeviceId()) return;
try {
// 简单实现:先清空本地数据,再添加同步数据
// 实际应用可能需要更复杂的冲突解决策略
const localWords = await vocabularyDB.getAllWords();
// 删除本地不存在于远程的数据
for (const localWord of localWords) {
if (!words.some(w => w.id === localWord.id)) {
await vocabularyDB.deleteWord(localWord.id!);
}
// 添加或更新远程数据
for (const word of words) {
const exists = localWords.some(w => w.id === word.id);
if (exists) {
await vocabularyDB.updateWord(word);
else {
await vocabularyDB.addWord(word);
}
this.notifyListeners('fullSync', words);
catch (err) {
console.error('全量同步失败:', JSON.stringify(err));
}
private notifyListeners(action: ‘add’ ‘update’ ‘delete’ ‘fullSync’, wordOrWords: Word
Word[]) {
this.listeners.forEach(listener => {
switch (action) {
case ‘add’:
listener.onWordAdded(wordOrWords as Word);
break;
case ‘update’:
listener.onWordUpdated(wordOrWords as Word);
break;
case ‘delete’:
listener.onWordDeleted(wordOrWords as Word);
break;
case ‘fullSync’:
listener.onFullSync(wordOrWords as Word[]);
break;
});
private getDeviceId(): string {
// 实际实现需要获取设备ID
return 'local_device';
}
interface SyncListener {
onWordAdded(word: Word): void;
onWordUpdated(word: Word): void;
onWordDeleted(word: Word): void;
onFullSync(words: Word[]): void;
export const vocabularySync = VocabularySync.getInstance();
单词本主界面(ArkUI)
// VocabularyApp.ets
import { vocabularyDB, vocabularySync } from ‘./VocabularyDB’;
@Entry
@Component
struct VocabularyApp {
@State words: Word[] = [];
@State searchKeyword: string = ‘’;
@State showAddDialog: boolean = false;
@State newWord: Word = { word: ‘’, translation: ‘’ };
private syncListener: SyncListener = {
onWordAdded: (word) => this.refreshWords(),
onWordUpdated: (word) => this.refreshWords(),
onWordDeleted: (word) => this.refreshWords(),
onFullSync: (words) => this.words = words
};
aboutToAppear() {
this.refreshWords();
vocabularySync.addListener(this.syncListener);
aboutToDisappear() {
vocabularySync.removeListener(this.syncListener);
build() {
Column() {
// 搜索栏
this.buildSearchBar()
// 单词列表
this.buildWordList()
// 添加按钮
Button('添加单词')
.margin(20)
.onClick(() => {
this.showAddDialog = true;
})
}
@Builder
buildSearchBar() {
Row() {
TextInput({ placeholder: ‘搜索单词’, text: this.searchKeyword })
.onChange((value: string) => {
this.searchKeyword = value;
this.searchWords();
})
.layoutWeight(1)
if (this.searchKeyword) {
Button('×')
.onClick(() => {
this.searchKeyword = '';
this.refreshWords();
})
}
.padding(10)
@Builder
buildWordList() {
List({ space: 10 }) {
ForEach(this.words, (word) => {
ListItem() {
this.buildWordItem(word)
})
.layoutWeight(1)
@Builder
buildWordItem(word: Word) {
Row() {
Column() {
Text(word.word)
.fontSize(18)
.fontWeight(FontWeight.Bold)
if (word.translation) {
Text(word.translation)
.fontSize(14)
.margin({ top: 4 })
if (word.example) {
Text(例句: ${word.example})
.fontSize(12)
.margin({ top: 4 })
}
.layoutWeight(1)
Row() {
Button('编辑')
.onClick(() => {
this.editWord(word);
})
Button('删除')
.margin({ left: 10 })
.onClick(() => {
this.deleteWord(word);
})
}
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ left: 10, right: 10, top: 5, bottom: 5 })
@Builder
buildAddDialog() {
if (this.showAddDialog) {
DialogComponent({
title: ‘添加单词’,
content: this.buildWordForm(),
onConfirm: () => this.addWord(),
onCancel: () => this.showAddDialog = false
})
}
@Builder
buildWordForm() {
Column() {
TextInput({ placeholder: ‘单词’, text: this.newWord.word })
.onChange((value: string) => {
this.newWord.word = value;
})
TextInput({ placeholder: '翻译', text: this.newWord.translation || '' })
.onChange((value: string) => {
this.newWord.translation = value;
})
.margin({ top: 10 })
TextInput({ placeholder: '例句(可选)', text: this.newWord.example || '' })
.onChange((value: string) => {
this.newWord.example = value;
})
.margin({ top: 10 })
.padding(10)
private async refreshWords() {
if (this.searchKeyword) {
this.words = await vocabularyDB.searchWords(this.searchKeyword);
else {
this.words = await vocabularyDB.getAllWords();
}
private async searchWords() {
if (this.searchKeyword) {
this.words = await vocabularyDB.searchWords(this.searchKeyword);
else {
this.refreshWords();
}
private async addWord() {
if (!this.newWord.word) return;
const rowId = await vocabularyDB.addWord(this.newWord);
if (rowId > 0) {
this.newWord.id = rowId;
await vocabularySync.syncWord(this.newWord, 'add');
this.showAddDialog = false;
this.newWord = { word: '', translation: '' };
this.refreshWords();
}
private editWord(word: Word) {
this.newWord = { …word };
this.showAddDialog = true;
private async updateWord() {
if (!this.newWord.word) return;
const success = await vocabularyDB.updateWord(this.newWord);
if (success) {
await vocabularySync.syncWord(this.newWord, 'update');
this.showAddDialog = false;
this.newWord = { word: '', translation: '' };
this.refreshWords();
}
private async deleteWord(word: Word) {
const success = await vocabularyDB.deleteWord(word.id!);
if (success) {
await vocabularySync.syncWord(word, ‘delete’);
this.refreshWords();
}
三、项目配置
权限配置
// module.json5
“module”: {
"requestPermissions": [
“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,
"reason": "跨设备同步单词本"
],
"abilities": [
“name”: “MainAbility”,
"type": "page",
"visible": true
],
"distributedNotification": {
"scenarios": [
“name”: “vocabulary_sync”,
"value": "word_list"
]
}
资源文件
<!-- resources/base/element/string.json -->
“string”: [
“name”: “app_name”,
"value": "单词本"
},
“name”: “search_hint”,
"value": "搜索单词"
},
“name”: “add_button”,
"value": "添加单词"
},
“name”: “word_hint”,
"value": "单词"
},
“name”: “translation_hint”,
"value": "翻译"
},
“name”: “example_hint”,
"value": "例句(可选)"
},
“name”: “add_dialog_title”,
"value": "添加单词"
},
“name”: “edit_button”,
"value": "编辑"
},
“name”: “delete_button”,
"value": "删除"
]
四、功能扩展
单词记忆功能
// 在Word接口中添加记忆相关字段
interface Word {
// …其他字段
memoryLevel?: number; // 0-5
lastReviewTime?: number;
nextReviewTime?: number;
// 在VocabularyDB中添加记忆功能方法
class VocabularyDB {
// …其他方法
public async reviewWord(id: number, level: number): Promise<boolean> {
const word = await this.getWordById(id);
if (!word) return false;
// 基于艾宾浩斯遗忘曲线计算下次复习时间
const now = Date.now();
const intervals = [1, 3, 7, 14, 30]; // 天数
const nextReviewTime = level < intervals.length ?
now + intervals[level] 24 60 60 1000 :
now + 60 24 60 60 1000; // 60天后
const valueBucket: relationalStore.ValuesBucket = {
'memory_level': level,
'last_review_time': now,
'next_review_time': nextReviewTime,
'update_time': now
};
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', id);
try {
const rowsUpdated = await this.rdbStore.update(valueBucket, predicates);
return rowsUpdated > 0;
catch (err) {
console.error('复习单词失败:', JSON.stringify(err));
return false;
}
public async getWordsToReview(): Promise<Word[]> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.lessThanOrEqualTo(‘next_review_time’, Date.now());
predicates.orderByAsc(‘next_review_time’);
try {
const resultSet = await this.rdbStore.query(predicates, [
'id', 'word', 'translation', 'example', 'memory_level'
]);
const words: Word[] = [];
while (resultSet.goToNextRow()) {
words.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
word: resultSet.getString(resultSet.getColumnIndex('word')),
translation: resultSet.getString(resultSet.getColumnIndex('translation')),
example: resultSet.getString(resultSet.getColumnIndex('example')),
memoryLevel: resultSet.getLong(resultSet.getColumnIndex('memory_level'))
});
resultSet.close();
return words;
catch (err) {
console.error('查询复习单词失败:', JSON.stringify(err));
return [];
}
// …其他方法
单词发音功能
// 使用媒体服务实现发音功能
import media from ‘@ohos.multimedia.media’;
class PronunciationService {
private audioPlayer: media.AudioPlayer;
async playPronunciation(word: string) {
try {
// 初始化音频播放器
this.audioPlayer = await media.createAudioPlayer();
// 设置音频源(实际应用中需要获取单词发音的音频文件)
const audioSrc = this.getAudioSource(word);
this.audioPlayer.src = audioSrc;
// 播放音频
this.audioPlayer.play();
catch (err) {
console.error('播放发音失败:', JSON.stringify(err));
}
private getAudioSource(word: string): string {
// 实际实现需要获取单词发音的音频URL
return https://example.com/pronunciation/${word}.mp3;
}
export const pronunciationService = new PronunciationService();
单词导入导出
// 实现单词本的导入导出功能
import fileIO from ‘@ohos.fileio’;
class VocabularyIO {
async exportToFile(words: Word[]): Promise<boolean> {
const filePath = ‘/data/storage/el2/base/files/vocabulary_export.json’;
try {
const content = JSON.stringify(words);
await fileIO.writeFile(filePath, content);
return true;
catch (err) {
console.error('导出单词本失败:', JSON.stringify(err));
return false;
}
async importFromFile(): Promise<Word[]> {
const filePath = ‘/data/storage/el2/base/files/vocabulary_export.json’;
try {
const content = await fileIO.readFile(filePath);
return JSON.parse(content) as Word[];
catch (err) {
console.error('导入单词本失败:', JSON.stringify(err));
return [];
}
export const vocabularyIO = new VocabularyIO();
五、总结
通过这个单词本应用的实现,我们学习了:
使用HarmonyOS关系型数据库存储结构化数据
实现基本的CRUD操作(创建、读取、更新、删除)
利用分布式能力实现多设备数据同步
构建响应式的用户界面
实现本地搜索功能
这个应用可以进一步扩展为功能更完善的单词记忆工具,如:
添加单词记忆曲线和复习提醒
集成单词发音功能
支持单词本导入导出
添加单词测试和记忆游戏
支持多单词本分类管理
分布式同步能力可以确保用户在不同设备上都能访问到最新的单词本数据,实现无缝的学习体验。
