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

在这里插入图片描述

图1:设置中心页面布局

前言

在 [第 23 篇]中,我们实现了主题切换。本文将实现完整的 设置中心,让用户可以统一管理 AI 配置、主题、数据等。

设置中心 是用户自定义 APP 行为的核心入口。良好的设置设计应分类清晰、操作直观、修改即时生效。


一、设置页面架构

设置分组包含项依赖模块
AI 设置模型切换、API Key、参数配置ProviderFactory
主题设置浅色/深色/自动、字体大小ThemeManager
数据管理清除缓存、导出数据、清除所有CacheManager
关于版本号、开源许可、反馈

二、SettingPage 实现

2.1 核心代码

// 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 数据导出格式

{
  "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
主题模式stringPreferences
模型参数number[]Preferences
聊天记录JSONPersistenceV2
缓存数据Map内存 + Preferences

四、设置项详解

4.1 AI 设置

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();
  }
}
设置项类型默认值说明
模型选择stringgpt-4o-miniAI 对话模型
API Key加密 string第三方 API 密钥
Temperaturenumber0.7生成随机性
Max Tokensnumber4096最大生成长度
系统 Promptstring默认自定义系统指令

4.2 数据管理

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 设置页面路由

// 设置页面的路由配置
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 获取安全区高度

// 在 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 安全区适配

@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.heightpx / density = vp状态栏占位
底部bottomRect.heightpx / 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”
闪电纯文字 “模型参数”
调色盘纯文字 “主题模式”
尺子纯文字 “字体大小”
垃圾桶纯文字 “清除缓存”
导出纯文字 “导出数据”
警告纯文字 “清除所有数据”
图钉纯文字 “版本号”
文档纯文字 “开源许可”
对话纯文字 “反馈建议”
// 正确的设置项:使用纯文字,不依赖 emoji
this.settingItem('模型切换', 'OpenAI GPT-4o-mini', () => {
  RouterUtil.navigateTo('pages/ModelSelectorPage');
});

七、设置页面最佳实践

在实现设置中心时,以下实践经验可以帮助你避免常见问题:

  1. 配置即时保存:用户修改设置后应立即持久化,不要等用户手动点击保存
  2. 敏感信息加密:API Key 等敏感信息必须使用加密存储,禁止明文保存
  3. 危险操作二次确认:清除数据等不可逆操作必须弹出确认对话框
  4. 状态同步:设置变更后应通知所有订阅者,确保 UI 状态一致
  5. 安全区适配:全屏页面必须处理顶部状态栏和底部导航栏的避让
实践项正确做法错误做法
API Key 存储SimpleCipher 加密后存 Preferences明文存储在内存或文件
缓存清除清除后刷新 UI 显示仅清除数据不更新界面
数据导出异步执行,显示 Loading同步阻塞主线程
主题切换通过 AppStorage 全局通知仅修改当前页面颜色
安全区px 转 vp 后设置 padding直接使用 px 值

提示:设置页面的用户体验直接影响用户对 APP 专业度的判断。细节处理越到位,用户信任度越高。


八、Git 提交

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

总结

本文实现了 设置中心。核心要点:

  1. 四大设置组:AI / 主题 / 数据 / 关于,分类清晰
  2. 一键操作:清缓存、导数据、清除所有
  3. 安全确认:危险操作二次确认
  4. 即时生效:修改配置立即保存
  5. 安全区适配:顶部和底部正确处理,避免超出屏幕
  6. SVG 图标:所有界面元素使用矢量图,避免 emoji 渲染异常

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐