HarmonyOS NEXT AI 智能生活助手:设置中心开发

图1:设置中心页面布局
前言
在 第 23 篇中,我们实现了主题切换。本文将实现完整的 设置中心,让用户可以统一管理 AI 配置、主题、数据等。
设置中心 是用户自定义 APP 行为的核心入口。良好的设置设计应分类清晰、操作直观、修改即时生效。
一、设置页面架构
| 设置分组 | 包含项 | 依赖模块 |
|---|---|---|
| AI 设置 | 模型切换、API Key、参数配置 | ProviderFactory |
| 主题设置 | 浅色/深色/自动、字体大小 | ThemeManager |
| 数据管理 | 清除缓存、导出数据、清除所有 | CacheManager |
| 关于 | 版本号、开源许可、反馈 | --- |
二、SettingPage 实现
2.1 核心代码
typescript
// pages/SettingPage.ets
@Entry
@Component
struct SettingPage {
@State cacheSize: string = '计算中...';
@State appVersion: string = 'v1.0.0';
aboutToAppear() {
this.calcCacheSize();
}
async calcCacheSize() {
const stats = CacheManager.getInstance().getStats();
this.cacheSize = `${stats.memorySize}/${stats.memoryMax} 条`;
}
build() {
Column() {
// 导航栏
Row() {
Image($r('app.media.ic_back')).width(24).height(24).onClick(() => RouterUtil.back());
Text('⚙️ 设置').fontSize(18).fontWeight(FontWeight.Bold).margin({ left: 12 });
}
.width('100%').height(56).padding({ left: 16, right: 16 });
Scroll() {
Column() {
// AI 设置组
this.sectionHeader('AI 设置');
this.settingItem('🤖 模型切换', 'OpenAI GPT-4o-mini', () => {
RouterUtil.navigateTo('pages/ModelSelectorPage');
});
this.settingItem('🔑 API Key', '已配置', () => {
RouterUtil.navigateTo('pages/ModelConfigPage');
});
this.settingItem('⚡ 模型参数', '温度/最大Token', () => {
RouterUtil.navigateTo('pages/ModelConfigPage');
});
// 主题设置组
this.sectionHeader('主题设置');
this.settingItem('🎨 主题模式', '浅色', () => {
RouterUtil.navigateTo('pages/ThemePage');
});
this.settingItem('📏 字体大小', '标准', () => {
// 字体大小选择
});
// 数据管理组
this.sectionHeader('数据管理');
this.settingItem('🗑️ 清除缓存', this.cacheSize, () => {
CacheManager.getInstance().clear();
this.cacheSize = '0 条';
ToastUtil.show('缓存已清除');
});
this.settingItem('📤 导出数据', '', () => {
this.exportData();
});
this.settingItem('⚠️ 清除所有数据', '', () => {
this.confirmClearAll();
});
// 关于组
this.sectionHeader('关于');
this.settingItem('📌 版本号', this.appVersion, () => {});
this.settingItem('📄 开源许可', 'Apache 2.0', () => {});
this.settingItem('💬 反馈建议', '', () => {
// 打开反馈页面
});
}
.padding(16);
}
.layoutWeight(1);
}
.width('100%').height('100%').backgroundColor('#F5F6FA');
}
@Builder
sectionHeader(title: string) {
Text(title).fontSize(13).fontColor('#6C5CE7').fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 8 });
}
@Builder
settingItem(label: string, value: string, onClick: () => void) {
Row() {
Text(label).fontSize(15);
Blank();
Text(value).fontSize(13).fontColor(Color.Gray).margin({ right: 4 });
Image($r('app.media.ic_chevron_right')).width(16).height(16);
}
.width('100%').padding(16).backgroundColor(Color.White).borderRadius(12)
.margin({ bottom: 6 }).onClick(() => onClick());
}
async exportData() {
try {
const data = await DataExporter.exportToJson();
const clipboard = getContext(this).clipboard;
await clipboard.set({ primary: data });
ToastUtil.show('数据已复制到剪贴板');
} catch {
ToastUtil.show('导出失败');
}
}
confirmClearAll() {
AlertDialog.show({
title: '确认清除',
message: '此操作将清除所有对话记录、设置和缓存数据,不可恢复!',
primaryButton: { value: '取消', action: () => {} },
secondaryButton: {
value: '确认清除',
fontColor: '#E17055',
action: () => {
this.clearAllData();
}
}
});
}
async clearAllData() {
await DatabaseManager.getInstance().getStore()?.executeSql('DELETE FROM conversation');
await DatabaseManager.getInstance().getStore()?.executeSql('DELETE FROM chat_message');
CacheManager.getInstance().clear();
ToastUtil.show('所有数据已清除');
}
}
三、功能详解
3.1 数据导出格式
json
{
"version": "1.0",
"exportTime": "2025-01-15T10:30:00.000Z",
"conversations": [
{
"id": "conv_001",
"title": "AI 开发讨论",
"messages": [
{ "role": "user", "content": "如何实现流式输出?" },
{ "role": "assistant", "content": "使用 AsyncGenerator..." }
]
}
],
"settings": {
"provider": "OpenAI",
"model": "gpt-4o-mini",
"theme": "light"
}
}
| 设置项 | 值类型 | 存储方式 |
|---|---|---|
| API Key | 加密字符串 | Preferences |
| 主题模式 | string | Preferences |
| 模型参数 | number\[\] | Preferences |
| 聊天记录 | JSON | PersistenceV2 |
| 缓存数据 | Map | 内存 + Preferences |
四、设置项详解
4.1 AI 设置
typescript
export class AISettingsManager {
private preferences: Preferences | null = null;
async init(context: Context): Promise<void> {
this.preferences = await getPreferences(context, 'ai_settings');
}
// 获取当前模型
async getCurrentModel(): Promise<string> {
return this.preferences?.get('model', 'gpt-4o-mini') as string || 'gpt-4o-mini';
}
// 设置 API Key
async setApiKey(key: string): Promise<void> {
if (!this.preferences) return;
const encrypted = SimpleCipher.encrypt(key);
await this.preferences.put('api_key', encrypted);
await this.preferences.flush();
}
// 获取 API Key(解密)
async getApiKey(): Promise<string> {
if (!this.preferences) return '';
const encrypted = await this.preferences.get('api_key', '') as string;
return encrypted ? SimpleCipher.decrypt(encrypted) : '';
}
// 清除 API Key
async clearApiKey(): Promise<void> {
if (!this.preferences) return;
await this.preferences.delete('api_key');
await this.preferences.flush();
}
}
| 设置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| 模型选择 | string | gpt-4o-mini | AI 对话模型 |
| API Key | 加密 string | 空 | 第三方 API 密钥 |
| Temperature | number | 0.7 | 生成随机性 |
| Max Tokens | number | 4096 | 最大生成长度 |
| 系统 Prompt | string | 默认 | 自定义系统指令 |
4.2 数据管理
typescript
export class DataManager {
private databaseManager = DatabaseManager.getInstance();
private cacheManager = CacheManager.getInstance();
// 导出所有数据为 JSON
async exportAllData(): Promise<string> {
const store = this.databaseManager.getStore();
const predicates = new relationalStore.RdbPredicates('conversation');
const resultSet = await store.query(predicates, ['*']);
const conversations: any[] = [];
while (resultSet.goToNextRow()) {
conversations.push({
id: resultSet.getString(0),
title: resultSet.getString(1),
messageCount: resultSet.getLong(9)
});
}
resultSet.close();
return JSON.stringify({
version: '1.0',
exportTime: new Date().toISOString(),
conversations,
cacheStats: this.cacheManager.getStats()
}, null, 2);
}
// 清除所有数据
async clearAll(): Promise<void> {
const store = this.databaseManager.getStore();
await store.executeSql('DELETE FROM chat_message');
await store.executeSql('DELETE FROM conversation');
this.cacheManager.clear();
}
// 获取存储用量
async getStorageUsage(): Promise<StorageInfo> {
const store = this.databaseManager.getStore();
const convCount = await store.query(
new relationalStore.RdbPredicates('conversation'), ['count(*) as cnt']
);
const msgCount = await store.query(
new relationalStore.RdbPredicates('chat_message'), ['count(*) as cnt']
);
let convs = 0, msgs = 0;
if (convCount.goToFirstRow()) convs = convCount.getLong(0);
if (msgCount.goToFirstRow()) msgs = msgCount.getLong(0);
convCount.close();
msgCount.close();
return {
conversationCount: convs,
messageCount: msgs,
cacheSize: this.cacheManager.getStats().memorySize,
lastClearTime: Date.now()
};
}
}
interface StorageInfo {
conversationCount: number;
messageCount: number;
cacheSize: number;
lastClearTime: number;
}
4.3 设置页面路由
typescript
// 设置页面的路由配置
export const SETTING_ROUTES = {
model: 'pages/ModelConfigPage',
theme: 'pages/ThemePage',
apiKey: 'pages/ModelConfigPage',
about: 'pages/AboutPage',
feedback: 'pages/FeedbackPage'
} as const;
// 设置项与路由映射
export function getSettingRoute(key: string): string {
return SETTING_ROUTES[key as keyof typeof SETTING_ROUTES] || '';
}
五、安全区适配
设置页面作为全屏页面,同样需要处理顶部状态栏和底部导航栏的安全区。
5.1 获取安全区高度
typescript
// 在 EntryAbility.onWindowStageCreate 中统一计算
import { display } from '@kit.ArkUI';
const displayInfo = display.getDefaultDisplaySync();
const densityPixels = displayInfo.densityPixels;
const statusBarHeightPx = avoidArea.topRect.height;
const navBarHeightPx = avoidArea.bottomRect.height;
// px 转 vp
const statusBarHeight = densityPixels > 0 ? statusBarHeightPx / densityPixels : statusBarHeightPx;
const navBarHeight = densityPixels > 0 ? navBarHeightPx / densityPixels : navBarHeightPx;
AppStorage.setOrCreate('statusBarHeight', statusBarHeight);
AppStorage.setOrCreate('navBarHeight', navBarHeight);
5.2 SettingPage 安全区适配
typescript
@Entry
@Component
struct SettingPage {
@StorageLink('statusBarHeight') statusBarHeight: number = 0;
@StorageLink('navBarHeight') navBarHeight: number = 0;
build() {
Column() {
// 顶部安全区占位
Row().width('100%').height(this.statusBarHeight);
// 导航栏
Row() {
Image($r('app.media.ic_back')).width(24).height(24).onClick(() => RouterUtil.back());
Text('设置').fontSize(18).fontWeight(FontWeight.Bold).margin({ left: 12 });
}
.width('100%').height(56).padding({ left: 16, right: 16 });
Scroll() {
Column() {
// 设置内容...
}
.padding(16);
}
.layoutWeight(1);
// 底部安全区占位
Row().width('100%').height(this.navBarHeight);
}
.width('100%').height('100%').backgroundColor('#F5F6FA');
}
}
| 安全区位置 | 获取方式 | 单位转换 | 用途 |
|---|---|---|---|
| 顶部 | topRect.height | px / density = vp | 状态栏占位 |
| 底部 | bottomRect.height | px / density = vp | 导航栏占位 |
注意 :
getWindowAvoidArea()返回的高度是 px (物理像素),而 ArkUI 的 padding/height 使用 vp (虚拟像素)。必须通过display.getDefaultDisplaySync().densityPixels获取屏幕密度后进行除法转换,否则 padding 会被放大 2~3 倍,导致顶部空白过大。
六、图标资源规范
根据项目实际开发经验,HarmonyOS NEXT 设备上 emoji 会渲染为蓝色或紫色方块。因此所有图标必须使用 SVG 矢量图。
6.1 SVG 图标替换示例
| 原 emoji | 替换方式 | SVG 资源名 |
|---|---|---|
| 设置齿轮 | 纯文字 "设置" | --- |
| 机器人 | 纯文字 "模型切换" | --- |
| 钥匙 | 纯文字 "API Key" | --- |
| 闪电 | 纯文字 "模型参数" | --- |
| 调色盘 | 纯文字 "主题模式" | --- |
| 尺子 | 纯文字 "字体大小" | --- |
| 垃圾桶 | 纯文字 "清除缓存" | --- |
| 导出 | 纯文字 "导出数据" | --- |
| 警告 | 纯文字 "清除所有数据" | --- |
| 图钉 | 纯文字 "版本号" | --- |
| 文档 | 纯文字 "开源许可" | --- |
| 对话 | 纯文字 "反馈建议" | --- |
typescript
// 正确的设置项:使用纯文字,不依赖 emoji
this.settingItem('模型切换', 'OpenAI GPT-4o-mini', () => {
RouterUtil.navigateTo('pages/ModelSelectorPage');
});
七、设置页面最佳实践
在实现设置中心时,以下实践经验可以帮助你避免常见问题:
- 配置即时保存:用户修改设置后应立即持久化,不要等用户手动点击保存
- 敏感信息加密:API Key 等敏感信息必须使用加密存储,禁止明文保存
- 危险操作二次确认:清除数据等不可逆操作必须弹出确认对话框
- 状态同步:设置变更后应通知所有订阅者,确保 UI 状态一致
- 安全区适配:全屏页面必须处理顶部状态栏和底部导航栏的避让
| 实践项 | 正确做法 | 错误做法 |
|---|---|---|
| API Key 存储 | SimpleCipher 加密后存 Preferences | 明文存储在内存或文件 |
| 缓存清除 | 清除后刷新 UI 显示 | 仅清除数据不更新界面 |
| 数据导出 | 异步执行,显示 Loading | 同步阻塞主线程 |
| 主题切换 | 通过 AppStorage 全局通知 | 仅修改当前页面颜色 |
| 安全区 | px 转 vp 后设置 padding | 直接使用 px 值 |
提示:设置页面的用户体验直接影响用户对 APP 专业度的判断。细节处理越到位,用户信任度越高。
八、Git 提交
bash
git add .
git commit -m "feat(setting): 设置中心开发
- AI 设置(模型/API Key/参数)
- 主题设置(模式/字体)
- 数据管理(缓存/导出/清除)
- 存储用量统计
- API Key 加密存储
- 安全区适配
- 关于信息
Co-Authored-By: AtomCode (deepseek-v4-flash) <noreply@atomgit.com>"
git tag v0.2.3
总结
本文实现了 设置中心。核心要点:
- 四大设置组:AI / 主题 / 数据 / 关于,分类清晰
- 一键操作:清缓存、导数据、清除所有
- 安全确认:危险操作二次确认
- 即时生效:修改配置立即保存
- 安全区适配:顶部和底部正确处理,避免超出屏幕
- SVG 图标:所有界面元素使用矢量图,避免 emoji 渲染异常
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: