基于HarmonyOS的笔记管理应用

这是我用ai辅助做出的一个小项目,emmmm,主包知道自己菜,还在学,后面会持续学习

Github仓库地址:https://github.com/iwait3/-HarmonyOS-.git

老规矩,先放运行截图,然后放开发指南

以上是使用模拟机运行出来的截图,之前测试放了些样例进去,接下来就是开发指南了

复制代码
这是一个基于HarmonyOS的现代笔记管理应用,采用ArkTS语言开发,具有以下核心特性:

- 现代化UI设计,符合HarmonyOS设计语言
- 支持文本、图片、文档、音频等多种内容类型
- 强大的搜索功能(标题、标签、内容多维度搜索)
- 批量操作支持
- 撤销删除功能
- 本地数据持久化存储

目录结构如下:

复制代码
note/
├── AppScope/                    # 应用级配置
│   ├── app.json5               # 应用配置文件
│   └── resources/              # 应用资源
├── entry/                      # 主模块
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/   # 入口能力
│   │   │   ├── model/          # 数据模型
│   │   │   ├── pages/          # 页面组件
│   │   │   └── Service/        # 服务层
│   │   ├── resources/          # 模块资源
│   │   └── module.json5        # 模块配置
├── build-profile.json5         # 构建配置
└── oh-package.json5           # 依赖管理

技术架构:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                        UI层 (Pages)                         │
├─────────────────────────────────────────────────────────────┤
│ Index.ets - 笔记列表页面  │  EditNote.ets - 编辑页面        │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                     服务层 (Services)                        │
├─────────────────────────────────────────────────────────────┤
│ NoteService - 数据管理  │ FilePickerService - 文件选择      │
│ ConfirmationService - 确认服务 │ UndoManager - 撤销管理      │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                     数据层 (Model)                          │
├─────────────────────────────────────────────────────────────┤
│                NoteData - 笔记数据模型                      │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                     HarmonyOS SDK                           │
├─────────────────────────────────────────────────────────────┤
│   Preferences  │ Router  │ FilePicker  │ PromptAction       │
└─────────────────────────────────────────────────────────────┘

1、配置应用信息 AppScope/app.json5

复制代码
{
  "app": {
    "bundleName": "com.cn.myapplication",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:book",
    "label": "$string:app_name"
  }
}

2、 配置模块信息 entry/src/main/module.json5

复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "icon": "$media:book",
        "label": "$string:EntryAbility_label",
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["ohos.want.action.home"]
          }
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:read_imagevideo_reason"
      }
    ]
  }
}

3、数据模型设计

复制代码
创建数据模型文件 entry/src/main/ets/model/NoteData.ts:
复制代码
export enum NoteContentType {
  TEXT = 'text',
  IMAGE = 'image', 
  DOCUMENT = 'document',
  AUDIO = 'audio'
}

export interface NoteContentItem {
  type: NoteContentType;
  content: string;
  metadata?: Record<string, string | number>;
  timestamp: string;
}

export interface NoteData {
  id: string;
  title: string;
  contentItems: NoteContentItem[];
  createTime: string;
  updateTime?: string;
  tags?: string[];
  isPinned?: boolean;
}

4、服务层实现

复制代码
1. **核心数据服务** - NoteService.ts
   - 使用Preferences进行数据持久化
   - 实现CRUD操作和缓存机制
   - 支持批量操作和搜索功能
复制代码
/**
 * 笔记服务 - 基于HarmonyOS最佳实践的笔记数据管理
 * 负责笔记数据的增删改查操作,使用Preferences进行持久化存储
 */

import dataPreferences from '@ohos.data.preferences';
import { NoteData } from '../model/NoteData';
import common from '@ohos.app.ability.common';

class NoteService {
  // 数据存储相关属性
  private preferences: dataPreferences.Preferences | null = null; // Preferences实例
  private readonly PREFERENCE_NAME = 'note_app_preferences';     // 存储文件名
  private readonly NOTES_KEY = 'notes';                          // 笔记数据键名
  private context: common.UIAbilityContext | null = null;         // 应用上下文
  private isInitialized: boolean = false;                         // 初始化状态

  // 缓存机制配置
  private notesCache: NoteData[] | null = null;                   // 笔记数据缓存
  private lastCacheUpdate: number = 0;                            // 最后缓存更新时间
  private readonly CACHE_TIMEOUT = 5000;                          // 5秒缓存有效期

  /**
   * 设置应用上下文
   * @param context - UIAbility上下文对象
   */
  setContext(context: common.UIAbilityContext): void {
    this.context = context;
  }

  /**
   * 初始化服务
   * 创建Preferences实例用于数据持久化
   */
  async init(): Promise<void> {
    if (this.isInitialized) {
      return;
    }

    if (!this.preferences && this.context) {
      try {
        this.preferences = await dataPreferences.getPreferences(this.context, this.PREFERENCE_NAME);
        this.isInitialized = true;
      } catch (error) {
        console.error('Failed to initialize preferences: ' + JSON.stringify(error));
        throw new Error('Preferences initialization failed');
      }
    }
  }

  /**
   * 检查缓存是否有效
   * @returns 缓存是否在有效期内
   */
  private isCacheValid(): boolean {
    return this.notesCache !== null && 
           Date.now() - this.lastCacheUpdate < this.CACHE_TIMEOUT;
  }

  /**
   * 获取所有笔记(带缓存优化)
   * @returns 笔记数据数组
   */
  async getAllNotes(): Promise<NoteData[]> {
    await this.init();
    
    if (!this.preferences) {
      console.error('Preferences not initialized');
      return [];
    }

    // 使用缓存机制减少IO操作
    if (this.isCacheValid()) {
      return this.notesCache!;
    }
    
    try {
      const notesJson = await this.preferences.get(this.NOTES_KEY, '[]');
      const notes = JSON.parse(notesJson as string) as NoteData[];
      
      // 更新缓存
      this.notesCache = notes;
      this.lastCacheUpdate = Date.now();
      
      return notes;
    } catch (error) {
      console.error('Failed to get notes: ' + JSON.stringify(error));
      return [];
    }
  }

  /**
   * 保存笔记
   * @param note - 笔记数据对象
   * @returns 保存是否成功
   */
  async saveNote(note: NoteData): Promise<boolean> {
    await this.init();
    
    if (!this.preferences) {
      console.error('Preferences not initialized');
      return false;
    }
    
    try {
      const notes = await this.getAllNotes();
      const existingIndex = notes.findIndex(n => n.id === note.id);

      // 更新笔记数据,添加更新时间
      const updatedNote = {
        ...note,
        updateTime: new Date().toISOString()
      };

      // 判断是新增还是更新
      if (existingIndex >= 0) {
        notes[existingIndex] = updatedNote;
      } else {
        updatedNote.createTime = new Date().toISOString();
        notes.push(updatedNote);
      }

      // 按更新时间降序排序,最新笔记显示在最前面
      notes.sort((a, b) => {
        const timeA = new Date(a.updateTime || a.createTime).getTime();
        const timeB = new Date(b.updateTime || b.createTime).getTime();
        return timeB - timeA;
      });

      // 保存到Preferences
      await this.preferences.put(this.NOTES_KEY, JSON.stringify(notes));
      await this.preferences.flush();
      
      // 清除缓存,确保下次读取最新数据
      this.notesCache = null;
      
      return true;
    } catch (error) {
      console.error('Failed to save note: ' + JSON.stringify(error));
      return false;
    }
  }

  /**
   * 删除笔记
   * @param id - 笔记ID
   * @returns 删除是否成功
   */
  async deleteNote(id: string): Promise<boolean> {
    await this.init();
    
    if (!this.preferences) {
      console.error('Preferences not initialized');
      return false;
    }
    
    try {
      const notes = await this.getAllNotes();
      const filteredNotes = notes.filter(note => note.id !== id);
      
      // 检查是否找到要删除的笔记
      if (filteredNotes.length === notes.length) {
        return false;
      }
      
      await this.preferences.put(this.NOTES_KEY, JSON.stringify(filteredNotes));
      await this.preferences.flush();
      
      this.notesCache = null;
      
      return true;
    } catch (error) {
      console.error('Failed to delete note: ' + JSON.stringify(error));
      return false;
    }
  }

  /**
   * 批量删除笔记
   * @param ids - 笔记ID数组
   * @returns 删除结果(成功和失败的ID列表)
   */
  async deleteNotes(ids: string[]): Promise<{ success: string[], failed: string[] }> {
    await this.init();
    
    if (!this.preferences) {
      console.error('Preferences not initialized');
      return { success: [], failed: ids };
    }
    
    try {
      const notes = await this.getAllNotes();
      const filteredNotes = notes.filter(note => !ids.includes(note.id));
      
      // 获取实际删除的笔记ID
      const deletedIds = notes
        .filter(note => ids.includes(note.id))
        .map(note => note.id);
      
      await this.preferences.put(this.NOTES_KEY, JSON.stringify(filteredNotes));
      await this.preferences.flush();
      
      this.notesCache = null;
      
      return { 
        success: deletedIds, 
        failed: ids.filter(id => !deletedIds.includes(id)) 
      };
    } catch (error) {
      console.error('Failed to delete notes: ' + JSON.stringify(error));
      return { success: [], failed: ids };
    }
  }

  /**
   * 批量保存笔记
   * @param notes - 笔记数据数组
   * @returns 保存是否成功
   */
  async batchSaveNotes(notes: NoteData[]): Promise<boolean> {
    await this.init();
    
    if (!this.preferences) {
      console.error('Preferences not initialized');
      return false;
    }
    
    try {
      const existingNotes = await this.getAllNotes();
      const updatedNotes = [...existingNotes];
      
      // 批量更新笔记数据
      for (const note of notes) {
        const existingIndex = updatedNotes.findIndex(n => n.id === note.id);
        if (existingIndex >= 0) {
          updatedNotes[existingIndex] = {
            ...note,
            updateTime: new Date().toISOString()
          };
        } else {
          updatedNotes.push({
            ...note,
            createTime: new Date().toISOString(),
            updateTime: new Date().toISOString()
          });
        }
      }
      
      // 排序
      updatedNotes.sort((a, b) => {
        const timeA = new Date(a.updateTime || a.createTime).getTime();
        const timeB = new Date(b.updateTime || b.createTime).getTime();
        return timeB - timeA;
      });
      
      await this.preferences.put(this.NOTES_KEY, JSON.stringify(updatedNotes));
      await this.preferences.flush();
      
      this.notesCache = null;
      
      return true;
    } catch (error) {
      console.error('Failed to batch save notes: ' + JSON.stringify(error));
      return false;
    }
  }

  /**
   * 根据ID获取笔记
   * @param id - 笔记ID
   * @returns 笔记数据或undefined
   */
  async getNoteById(id: string): Promise<NoteData | undefined> {
    await this.init();
    
    try {
      const notes = await this.getAllNotes();
      return notes.find(note => note.id === id);
    } catch (error) {
      console.error('Failed to get note by id: ' + JSON.stringify(error));
      return undefined;
    }
  }

  /**
   * 搜索笔记
   * @param query - 搜索关键词
   * @returns 匹配的笔记数组
   */
  async searchNotes(query: string): Promise<NoteData[]> {
    if (!query.trim()) {
      return await this.getAllNotes();
    }
    
    try {
      const notes = await this.getAllNotes();
      const searchTerm = query.toLowerCase();
      
      // 多维度搜索:标题、标签、文本内容
      return notes.filter(note => {
        // 搜索标题
        if (note.title && note.title.toLowerCase().includes(searchTerm)) {
          return true;
        }
        
        // 搜索标签
        if (note.tags && note.tags.some(tag => tag.toLowerCase().includes(searchTerm))) {
          return true;
        }
        
        // 搜索内容
        if (note.contentItems) {
          return note.contentItems.some(item => {
            if (item.type === 'text') {
              return item.content.toLowerCase().includes(searchTerm);
            }
            return false;
          });
        }
        
        return false;
      });
    } catch (error) {
      console.error('Failed to search notes: ' + JSON.stringify(error));
      return [];
    }
  }

  /**
   * 获取笔记数量统计
   * @returns 统计信息对象
   */
  async getStatistics(): Promise<{
    total: number;
    pinned: number;
    withImages: number;
    withDocuments: number;
    withAudio: number;
  }> {
    try {
      const notes = await this.getAllNotes();
      
      return {
        total: notes.length,
        pinned: notes.filter(note => note.isPinned).length,
        withImages: notes.filter(note => 
          note.contentItems?.some(item => item.type === 'image')
        ).length,
        withDocuments: notes.filter(note => 
          note.contentItems?.some(item => item.type === 'document')
        ).length,
        withAudio: notes.filter(note => 
          note.contentItems?.some(item => item.type === 'audio')
        ).length
      };
    } catch (error) {
      console.error('Failed to get statistics: ' + JSON.stringify(error));
      return { total: 0, pinned: 0, withImages: 0, withDocuments: 0, withAudio: 0 };
    }
  }

  /**
   * 清除缓存
   */
  clearCache(): void {
    this.notesCache = null;
    this.lastCacheUpdate = 0;
  }
}

// 导出单例实例
export default new NoteService();
复制代码
2. **文件选择服务** - FilePickerService.ts
   - 处理图片、文档、音频文件选择
   - 文件信息提取和验证
复制代码
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';

export interface FileInfo {
  uri: string;
  name: string;
  size: number;
  type: string;
  timestamp: string;
}

class FilePickerService {
  async pickImage(): Promise<FileInfo | null> {
    try {
      const photoPicker = new picker.PhotoViewPicker();
      const photoSelectOptions = new picker.PhotoSelectOptions();
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      
      const result = await photoPicker.select(photoSelectOptions);

      if (result && result.photoUris && result.photoUris.length > 0) {
        const uri = result.photoUris[0];
        const fileInfo = await this.getFileInfo(uri);
        return fileInfo;
      }
      return null;
    } catch (error) {
      console.error('Failed to pick image: ' + JSON.stringify(error));
      return null;
    }
  }

  async pickDocument(): Promise<FileInfo | null> {
    try {
      const documentPicker = new picker.DocumentViewPicker();
      const documentSelectOptions = new picker.DocumentSelectOptions();
      
      const result = await documentPicker.select(documentSelectOptions);

      if (result && result.length > 0) {
        const uri = result[0];
        const fileInfo = await this.getFileInfo(uri);
        return fileInfo;
      }
      return null;
    } catch (error) {
      console.error('Failed to pick document: ' + JSON.stringify(error));
      return null;
    }
  }

  async pickAudio(): Promise<FileInfo | null> {
    try {
      const audioPicker = new picker.AudioViewPicker();
      const audioSelectOptions = new picker.AudioSelectOptions();
      
      const result = await audioPicker.select(audioSelectOptions);

      if (result && result.length > 0) {
        const uri = result[0];
        const fileInfo = await this.getFileInfo(uri);
        return fileInfo;
      }
      return null;
    } catch (error) {
      console.error('Failed to pick audio: ' + JSON.stringify(error));
      return null;
    }
  }

  private async getFileInfo(uri: string): Promise<FileInfo> {
    try {
      const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
      const stat = fs.statSync(file.fd);
      fs.closeSync(file);

      return {
        uri: uri,
        name: this.getFileNameFromUri(uri),
        size: stat.size,
        type: this.getFileTypeFromUri(uri),
        timestamp: new Date().toISOString()
      };
    } catch (error) {
      console.error('Failed to get file info: ' + JSON.stringify(error));
      return {
        uri: uri,
        name: '未知文件',
        size: 0,
        type: 'unknown',
        timestamp: new Date().toISOString()
      };
    }
  }

  private getFileNameFromUri(uri: string): string {
    try {
      const segments = uri.split('/');
      let fileName = segments[segments.length - 1] || '未命名文件';
      
      // 解码URI编码的文件名
      try {
        fileName = decodeURIComponent(fileName);
      } catch (e) {
        console.log('文件名无需解码: ' + fileName);
      }
      
      // 美化显示:移除特殊字符,保留中文和基本字符
      fileName = fileName.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\-_.\s]/g, '').trim();
      
      return fileName || '未命名文件';
    } catch (error) {
      console.error('文件名处理失败: ' + JSON.stringify(error));
      return '未命名文件';
    }
  }

  private getFileTypeFromUri(uri: string): string {
    const extension = uri.split('.').pop()?.toLowerCase() || '';
    const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
    const documentExtensions = ['pdf', 'doc', 'docx', 'txt', 'rtf', 'md'];
    const audioExtensions = ['mp3', 'wav', 'aac', 'flac', 'ogg'];

    if (imageExtensions.includes(extension)) return 'image';
    if (documentExtensions.includes(extension)) return 'document';
    if (audioExtensions.includes(extension)) return 'audio';
    return 'unknown';
  }
}

export default new FilePickerService();
复制代码
3. **用户交互服务** - ConfirmationService.ts
   - 统一管理确认对话框
   - 提示信息和Toast管理
复制代码
import promptAction from '@ohos.promptAction';

/**
 * 操作风险级别
 */
export enum OperationRiskLevel {
  LOW = 'low',      // 低风险操作
  MEDIUM = 'medium', // 中风险操作
  HIGH = 'high',     // 高风险操作
  CRITICAL = 'critical' // 关键风险操作
}

/**
 * 确认弹窗服务 - 基于HarmonyOS最佳实践的统一确认操作管理
 */
class ConfirmationService {
  // 弹窗配置常量
  private readonly CONFIG = {
    DANGER_COLOR: '#FF3B30',
    PRIMARY_COLOR: '#007AFF',
    SECONDARY_COLOR: '#666666',
    SUCCESS_COLOR: '#34C759',
    WARNING_COLOR: '#FF9500'
  };

  /**
   * 根据风险级别获取确认配置
   */
  private getConfirmationConfig(
    level: OperationRiskLevel,
    operationType: string,
    itemName: string = '项目',
    count: number = 1
  ): {
    title: string;
    message: string;
    confirmText: string;
    cancelText: string;
    confirmColor: string;
    showWarningIcon: boolean;
    requireAdditionalConfirmation?: boolean;
  } {
    const baseConfigs = {
      [OperationRiskLevel.LOW]: {
        title: '确认操作',
        message: `确定要执行此操作吗?`,
        confirmText: '确定',
        cancelText: '取消',
        confirmColor: this.CONFIG.PRIMARY_COLOR,
        showWarningIcon: false
      },
      [OperationRiskLevel.MEDIUM]: {
        title: '操作确认',
        message: `确定要${operationType}这个${itemName}吗?`,
        confirmText: operationType,
        cancelText: '取消',
        confirmColor: this.CONFIG.WARNING_COLOR,
        showWarningIcon: true
      },
      [OperationRiskLevel.HIGH]: {
        title: '高风险操作确认',
        message: `确定要${operationType}这个${itemName}吗?此操作将无法撤销。`,
        confirmText: operationType,
        cancelText: '取消',
        confirmColor: this.CONFIG.DANGER_COLOR,
        showWarningIcon: true,
        requireAdditionalConfirmation: true
      },
      [OperationRiskLevel.CRITICAL]: {
        title: '关键操作确认',
        message: `确定要${operationType} ${count} 个${itemName}吗?此操作将永久删除数据且无法恢复。`,
        confirmText: `确认${operationType}`,
        cancelText: '取消',
        confirmColor: this.CONFIG.DANGER_COLOR,
        showWarningIcon: true,
        requireAdditionalConfirmation: true
      }
    };

    return baseConfigs[level];
  }

  /**
   * 显示分级确认弹窗
   */
  async showGradedConfirmation(
    level: OperationRiskLevel,
    operationType: string,
    itemName: string = '项目',
    count: number = 1
  ): Promise<boolean> {
    const config = this.getConfirmationConfig(level, operationType, itemName, count);
    
    // 高风险操作需要二次确认
    if (config.requireAdditionalConfirmation) {
      const firstConfirmed = await this.showBasicConfirmation(config);
      if (!firstConfirmed) {
        return false;
      }
      
      // 二次确认
      const secondConfig = {
        ...config,
        title: '请再次确认',
        message: `请再次确认是否要${operationType} ${count > 1 ? count + '个' : '这个'}${itemName}?`,
        confirmText: '确认执行'
      };
      
      return await this.showBasicConfirmation(secondConfig);
    }
    
    return await this.showBasicConfirmation(config);
  }

  /**
   * 显示基础确认弹窗
   */
  private async showBasicConfirmation(config: {
    title: string;
    message: string;
    confirmText: string;
    cancelText: string;
    confirmColor: string;
    showWarningIcon: boolean;
    requireAdditionalConfirmation?: boolean;
  }): Promise<boolean> {
    return new Promise((resolve) => {
      promptAction.showDialog({
        title: config.title,
        message: config.message,
        buttons: [
          {
            text: config.cancelText,
            color: this.CONFIG.SECONDARY_COLOR
          },
          {
            text: config.confirmText,
            color: config.confirmColor
          }
        ]
      }).then((result) => {
        resolve(result.index === 1);
      }).catch(() => {
        resolve(false);
      });
    });
  }

  /**
   * 显示删除确认弹窗 - 增强版
   */
  async showDeleteConfirmation(
    message: string = '确定要删除吗?删除后将无法恢复。',
    title: string = '删除确认',
    itemName: string = '项目'
  ): Promise<boolean> {
    return new Promise((resolve) => {
      promptAction.showDialog({
        title: title,
        message: message,
        buttons: [
          {
            text: '取消',
            color: this.CONFIG.SECONDARY_COLOR
          },
          {
            text: `删除${itemName}`,
            color: this.CONFIG.DANGER_COLOR
          }
        ]
      }).then((result) => {
        resolve(result.index === 1);
      }).catch(() => {
        resolve(false);
      });
    });
  }

  /**
   * 显示批量删除确认弹窗 - 增强版
   */
  async showBatchDeleteConfirmation(count: number, itemName: string = '笔记'): Promise<boolean> {
    const level = count > 5 ? OperationRiskLevel.CRITICAL : OperationRiskLevel.HIGH;
    return this.showGradedConfirmation(level, '删除', itemName, count);
  }

  /**
   * 显示内容删除确认弹窗 - 增强版
   */
  async showContentDeleteConfirmation(contentType: string = '内容项'): Promise<boolean> {
    return this.showGradedConfirmation(OperationRiskLevel.MEDIUM, '删除', contentType, 1);
  }

  /**
   * 显示笔记删除确认(智能分级)
   */
  async showNoteDeleteConfirmation(noteTitle: string, isPinned: boolean = false): Promise<boolean> {
    const level = isPinned ? OperationRiskLevel.HIGH : OperationRiskLevel.MEDIUM;
    
    // 智能消息生成
    let message = `确定要删除"${noteTitle}"吗?`;
    if (isPinned) {
      message += ' 这是一个置顶笔记,删除后将无法恢复。';
    } else {
      message += ' 删除后将无法恢复。';
    }
    
    return new Promise((resolve) => {
      promptAction.showDialog({
        title: isPinned ? '删除置顶笔记' : '删除笔记',
        message: message,
        buttons: [
          {
            text: '取消',
            color: this.CONFIG.SECONDARY_COLOR
          },
          {
            text: '确认删除',
            color: this.CONFIG.DANGER_COLOR
          }
        ]
      }).then((result) => {
        resolve(result.index === 1);
      }).catch(() => {
        resolve(false);
      });
    });
  }

  /**
   * 显示保存确认弹窗
   */
  async showSaveConfirmation(): Promise<boolean> {
    return this.showGradedConfirmation(OperationRiskLevel.LOW, '保存', '修改', 1);
  }

  /**
   * 显示取消编辑确认
   */
  async showCancelEditConfirmation(hasChanges: boolean): Promise<boolean> {
    if (!hasChanges) {
      return true;
    }
    
    return new Promise((resolve) => {
      promptAction.showDialog({
        title: '放弃修改',
        message: '当前有未保存的修改,确定要放弃吗?',
        buttons: [
          {
            text: '继续编辑',
            color: this.CONFIG.SECONDARY_COLOR
          },
          {
            text: '放弃修改',
            color: this.CONFIG.DANGER_COLOR
          }
        ]
      }).then((result) => {
        resolve(result.index === 1);
      }).catch(() => {
        resolve(false);
      });
    });
  }

  /**
   * 显示通用确认弹窗 - 增强版
   */
  async showConfirmation(
    message: string,
    title: string = '确认操作',
    confirmText: string = '确定',
    cancelText: string = '取消',
    confirmType: 'primary' | 'danger' | 'success' = 'primary'
  ): Promise<boolean> {
    return new Promise((resolve) => {
      const confirmColor = this.getConfirmColor(confirmType);
      
      promptAction.showDialog({
        title: title,
        message: message,
        buttons: [
          {
            text: cancelText,
            color: this.CONFIG.SECONDARY_COLOR
          },
          {
            text: confirmText,
            color: confirmColor
          }
        ]
      }).then((result) => {
        resolve(result.index === 1);
      }).catch(() => {
        resolve(false);
      });
    });
  }

  /**
   * 显示操作确认弹窗(带警告图标)
   */
  async showWarningConfirmation(
    message: string,
    title: string = '操作确认'
  ): Promise<boolean> {
    return this.showConfirmation(
      message,
      title,
      '继续',
      '取消',
      'danger'
    );
  }

  /**
   * 显示成功确认弹窗
   */
  async showSuccessConfirmation(
    message: string,
    title: string = '操作成功'
  ): Promise<boolean> {
    return this.showConfirmation(
      message,
      title,
      '确定',
      '',
      'success'
    );
  }

  /**
   * 显示撤销提示 - 增强版
   */
  showUndoToast(message: string = '操作已撤销', duration: number = 2000): void {
    promptAction.showToast({
      message: message,
      duration: duration
    });
  }

  /**
   * 显示操作成功提示 - 增强版
   */
  showSuccessToast(message: string, duration: number = 2000): void {
    promptAction.showToast({
      message: message,
      duration: duration
    });
  }

  /**
   * 显示错误提示 - 增强版
   */
  showErrorToast(message: string, duration: number = 3000): void {
    promptAction.showToast({
      message: message,
      duration: duration
    });
  }

  /**
   * 显示信息提示
   */
  showInfoToast(message: string, duration: number = 2000): void {
    promptAction.showToast({
      message: message,
      duration: duration
    });
  }

  /**
   * 显示警告提示
   */
  showWarningToast(message: string, duration: number = 3000): void {
    promptAction.showToast({
      message: message,
      duration: duration
    });
  }

  /**
   * 获取确认按钮颜色
   */
  private getConfirmColor(type: string): string {
    switch (type) {
      case 'danger':
        return this.CONFIG.DANGER_COLOR;
      case 'success':
        return this.CONFIG.SUCCESS_COLOR;
      case 'primary':
      default:
        return this.CONFIG.PRIMARY_COLOR;
    }
  }

  /**
   * 显示加载提示
   */
  showLoading(message: string = '加载中...'): void {
    // HarmonyOS 暂不支持自定义加载提示,使用Toast替代
    promptAction.showToast({
      message: message,
      duration: 1500
    });
  }

  /**
   * 批量操作结果提示
   */
  showBatchResultToast(successCount: number, totalCount: number, operation: string = '操作'): void {
    if (successCount === totalCount) {
      this.showSuccessToast(`${operation}成功,共处理 ${totalCount} 个项目`);
    } else if (successCount === 0) {
      this.showErrorToast(`${operation}失败,请重试`);
    } else {
      this.showWarningToast(`${operation}完成,成功 ${successCount}/${totalCount} 个项目`);
    }
  }
}

export default new ConfirmationService();

5、UI组件开发

复制代码
1. **主页面组件** - Index.ets
   - 笔记列表展示
   - 搜索和过滤功能
   - 批量操作支持
   - 撤销删除功能
复制代码
/**
 * 笔记列表页面 - 应用的主界面
 * 显示所有笔记列表,支持搜索、批量操作、撤销删除等功能
 */

import router from '@ohos.router';
import promptAction from '@ohos.promptAction';
import { NoteData } from '../model/NoteData';
import NoteService from '../Service/NoteService';
import UndoManager from '../Service/UndoManager';
import ConfirmationService from '../Service/ConfirmationService';

@Entry
@Component
struct Index {
  // 状态管理:使用@State装饰器实现响应式数据绑定
  @State notes: NoteData[] = [];                    // 所有笔记数据
  @State searchText: string = '';                   // 搜索关键词
  @State filteredNotes: NoteData[] = [];            // 过滤后的笔记列表
  @State selectedNotes: string[] = [];              // 批量选择中的笔记ID
  @State isBatchMode: boolean = false;              // 是否处于批量模式
  @State showUndoToast: boolean = false;            // 是否显示撤销提示

  /**
   * 页面即将显示时调用
   * 初始化数据和加载笔记
   */
  async aboutToAppear() {
    await this.loadNotes();
  }

  /**
   * 页面显示时调用
   * 确保数据最新状态
   */
  onPageShow() {
    this.loadNotes();
  }

  /**
   * 加载笔记数据
   * 从服务层获取所有笔记并更新UI
   */
  async loadNotes() {
    this.notes = await NoteService.getAllNotes();
    this.filteredNotes = this.notes;
  }

  /**
   * 搜索功能实现
   * 根据关键词过滤笔记列表
   */
  onSearch() {
    if (this.searchText) {
      // 多维度搜索:标题、标签、文本内容
      this.filteredNotes = this.notes.filter(note => {
        // 搜索标题
        if (note.title && note.title.includes(this.searchText)) {
          return true;
        }
        
        // 搜索标签
        if (note.tags && note.tags.some(tag => tag.includes(this.searchText))) {
          return true;
        }
        
        // 搜索文本内容
        if (note.contentItems) {
          return note.contentItems.some(item => {
            if (item.type === 'text') {
              return item.content.includes(this.searchText);
            }
            return false;
          });
        }
        
        return false;
      });
    } else {
      this.filteredNotes = this.notes;
    }
  }

  /**
   * 添加新笔记
   * 跳转到编辑页面创建新笔记
   */
  onAddNote() {
    router.push({
      url: 'pages/EditNote'
    });
  }

  /**
   * 笔记点击事件处理
   * 根据当前模式执行不同操作
   * @param note - 被点击的笔记对象
   */
  onNoteClick(note: NoteData) {
    if (this.isBatchMode) {
      // 批量模式:切换选择状态
      this.toggleNoteSelection(note.id);
    } else {
      // 正常模式:跳转到编辑页面
      router.push({
        url: 'pages/EditNote',
        params: {
          noteId: note.id
        }
      });
    }
  }

  /**
   * 切换笔记选择状态
   * @param noteId - 笔记ID
   */
  toggleNoteSelection(noteId: string) {
    const index = this.selectedNotes.indexOf(noteId);
    if (index > -1) {
      this.selectedNotes.splice(index, 1);
    } else {
      this.selectedNotes.push(noteId);
    }
  }

  /**
   * 删除单个笔记
   * @param noteId - 笔记ID
   */
  async deleteNote(noteId: string) {
    const noteToDelete = this.notes.find(note => note.id === noteId);
    if (!noteToDelete) {
      return;
    }

    // 显示确认对话框
    const confirmed = await ConfirmationService.showNoteDeleteConfirmation(
      noteToDelete.title || '未命名笔记',
      noteToDelete.isPinned
    );
    
    if (confirmed) {
      // 添加到撤销管理器
      UndoManager.addDeletedNotes([noteToDelete]);
      const success = await NoteService.deleteNote(noteId);
      
      if (success) {
        await this.loadNotes();
        this.showUndoToast = true;
        ConfirmationService.showSuccessToast('笔记删除成功');
        
        // 5秒后自动隐藏撤销提示
        setTimeout(() => {
          this.showUndoToast = false;
          UndoManager.clear();
        }, 5000);
      } else {
        ConfirmationService.showErrorToast('删除失败,请重试');
      }
    }
  }

  /**
   * 撤销删除操作
   * 从撤销管理器恢复已删除的笔记
   */
  async undoDelete() {
    if (UndoManager.hasUndo()) {
      const notesToRestore = await UndoManager.undo();
      const success = await NoteService.batchSaveNotes(notesToRestore);
      await this.loadNotes();
      this.showUndoToast = false;
      
      if (success) {
        ConfirmationService.showUndoToast('删除操作已撤销');
      } else {
        ConfirmationService.showErrorToast('撤销操作失败');
      }
    }
  }

  /**
   * 批量删除选中的笔记
   */
  async deleteSelectedNotes() {
    if (this.selectedNotes.length === 0) {
      ConfirmationService.showErrorToast('请先选择要删除的笔记');
      return;
    }

    const confirmed = await ConfirmationService.showBatchDeleteConfirmation(this.selectedNotes.length, '笔记');
    
    if (confirmed) {
      const notesToDelete = this.notes.filter(note => this.selectedNotes.includes(note.id));
      UndoManager.addDeletedNotes(notesToDelete);
      
      const deleteResult = await NoteService.deleteNotes(this.selectedNotes);
      
      if (deleteResult.failed.length > 0) {
        ConfirmationService.showBatchResultToast(
          deleteResult.success.length, 
          this.selectedNotes.length, 
          '删除'
        );
      } else {
        ConfirmationService.showSuccessToast(`成功删除 ${deleteResult.success.length} 个笔记`);
      }
      
      await this.loadNotes();
      this.selectedNotes = [];
      this.isBatchMode = false;
      this.showUndoToast = true;
      
      setTimeout(() => {
        this.showUndoToast = false;
        UndoManager.clear();
      }, 5000);
    }
  }

  /**
   * 切换批量操作模式
   */
  toggleBatchMode() {
    this.isBatchMode = !this.isBatchMode;
    if (!this.isBatchMode) {
      this.selectedNotes = [];
    }
  }

  /**
   * 获取笔记预览内容
   * @param note - 笔记对象
   * @returns 预览文本
   */
  private getNotePreview(note: NoteData): string {
    if (!note.contentItems || note.contentItems.length === 0) {
      return '暂无内容';
    }

    const firstItem = note.contentItems[0];
    if (firstItem.type === 'text') {
      return firstItem.content.length > 50 
        ? firstItem.content.substring(0, 50) + '...' 
        : firstItem.content;
    } else {
      // 文件类型预览
      const typeMap: Record<string, string> = {
        'image': '[图片]',
        'document': '[文档]',
        'audio': '[音频]'
      };
      return typeMap[firstItem.type] || '[文件]';
    }
  }

  /**
   * 格式化日期显示
   * @param dateString - 日期字符串
   * @returns 格式化后的日期文本
   */
  private formatDate(dateString: string): string {
    try {
      const date = new Date(dateString);
      const now = new Date();
      const diffMs = now.getTime() - date.getTime();
      const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
      
      // 相对时间显示
      if (diffDays === 0) {
        return '今天';
      } else if (diffDays === 1) {
        return '昨天';
      } else if (diffDays < 7) {
        return `${diffDays}天前`;
      } else {
        return date.toLocaleDateString();
      }
    } catch (error) {
      return dateString;
    }
  }

  /**
   * 构建侧滑删除按钮
   * @param note - 笔记对象
   * @returns 侧滑按钮组件
   */
  @Builder
  getSwipeActionButtons(note: NoteData) {
    Column() {
      Button() {
        Image($r('app.media.delete'))
          .width(18)
          .height(18)
          .fillColor('#ffffff')
      }
      .width(45)
      .height(45)
      .backgroundColor('#FF3B30')
      .borderRadius(8)
      .onClick(() => this.deleteNote(note.id))
    }
    .width(60)
    .height(60)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#FF3B30')
    .borderRadius(8)
  }

  /**
   * 页面UI构建
   */
  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      // 主内容区域
      Column({
        space: 0
      }) {
        // 顶部操作栏 - 现代简约设计
        Column({
          space: 8
        }) {
          // 搜索区域
          Row({
            space: 12
          }) {
            // 搜索框 - 简洁设计
            Row({
              space: 8
            }) {
              Image($r('app.media.search'))
                .width(20)
                .height(20)
                .fillColor('#666666')
                .margin({ left: 12 })
              
              TextInput({
                placeholder: '搜索笔记...',
                text: this.searchText
              })
                .onChange((value: string) => {
                  this.searchText = value;
                  this.onSearch();
                })
                .width('100%')
                .height(40)
                .fontSize(16)
                .fontColor('#333333')
                .placeholderColor('#999999')
                .backgroundColor('transparent')
            }
            .backgroundColor('#f8f9fa')
            .borderRadius(20)
            .flexGrow(1)
            .height(40)
            .margin({ left: 16, right: 16 })

            // 批量操作按钮
            Button() {
              Image(this.isBatchMode ? $r('app.media.add') : $r('app.media.more1'))
                .width(20)
                .height(20)
                .fillColor(this.isBatchMode ? '#007AFF' : '#666666')
            }
            .width(40)
            .height(40)
            .backgroundColor(this.isBatchMode ? '#e6f3ff' : 'transparent')
            .borderRadius(20)
            .margin({ right: 16 })
            .onClick(() => this.toggleBatchMode())
          }
          .width('100%')

          // 批量操作工具栏
          if (this.isBatchMode) {
            Row({
              space: 16
            }) {
              Text(`已选择 ${this.selectedNotes.length} 个笔记`)
                .fontSize(14)
                .fontColor('#666666')
                .flexGrow(1)
                .margin({ left: 16 })

              Button('删除选中')
                .width(80)
                .height(32)
                .fontSize(12)
                .backgroundColor('#FF3B30')
                .fontColor('#ffffff')
                .borderRadius(16)
                .margin({ right: 16 })
                .onClick(() => this.deleteSelectedNotes())
            }
            .width('100%')
            .padding({ top: 8, bottom: 8 })
            .backgroundColor('#f8f9fa')
            .borderRadius(8)
            .margin({ left: 16, right: 16 })
          }
        }
        .width('100%')
        .padding({ top: 12, bottom: 12 })
        .backgroundColor('#ffffff')

        // 笔记列表
        List() {
          ForEach(this.filteredNotes, (note: NoteData) => {
            ListItem() {
              Row({
                space: 12
              }) {
                if (this.isBatchMode) {
                  Image(this.selectedNotes.includes(note.id) ? $r('app.media.add') : $r('app.media.search'))
                    .width(20)
                    .height(20)
                    .fillColor(this.selectedNotes.includes(note.id) ? '#007AFF' : '#CCCCCC')
                }

                Column({
                  space: 6
                }) {
                  Row() {
                    Text(note.title || '未命名笔记')
                      .fontSize(16)
                      .fontWeight(FontWeight.Medium)
                      .fontColor('#333333')
                      .flexGrow(1)
                    
                    if (note.isPinned) {
                      Image($r('app.media.more1'))
                        .width(16)
                        .height(16)
                        .margin({ left: 8 })
                        .fillColor('#FF9500')
                    }
                  }
                  .width('100%')

                  Text(this.getNotePreview(note))
                    .fontSize(14)
                    .fontColor('#666666')
                    .width('100%')
                    .maxLines(2)

                  Row() {
                    if (note.tags && note.tags.length > 0) {
                      Text(note.tags.slice(0, 2).join(', '))
                        .fontSize(11)
                        .fontColor('#666666')
                        .backgroundColor('#f0f0f0')
                        .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                        .borderRadius(4)
                    }
                    
                    Text(this.formatDate(note.updateTime || note.createTime))
                      .fontSize(11)
                      .fontColor('#999999')
                      .margin({ left: 8 })
                  }
                  .width('100%')
                  .margin({ top: 4 })
                }
                .flexGrow(1)
              }
              .padding(16)
              .backgroundColor(this.selectedNotes.includes(note.id) ? '#f0f8ff' : '#ffffff')
              .borderRadius(12)
              .margin({ left: 16, right: 16, bottom: 8 })
              .border({
                width: 1,
                color: '#f0f0f0'
              })
              .onClick(() => this.onNoteClick(note))
            }
            .swipeAction({
              end: this.getSwipeActionButtons(note)
            })
          }, (note: NoteData) => note.id)
        }
        .width('100%')
        .flexGrow(1)
        .backgroundColor('#ffffff')

        // 撤销操作提示
        if (this.showUndoToast) {
          Row({
            space: 16
          }) {
            Text(`已删除 ${UndoManager.getUndoNotes().length} 个笔记`)
              .fontSize(14)
              .fontColor('#ffffff')
              .flexGrow(1)
              .margin({ left: 16 })

            Button('撤销')
              .width(50)
              .height(30)
              .fontSize(12)
              .backgroundColor('#ffffff')
              .fontColor('#007AFF')
              .borderRadius(4)
              .margin({ right: 16 })
              .onClick(() => this.undoDelete())
          }
          .width('100%')
          .height(50)
          .backgroundColor('#333333')
          .borderRadius(8)
          .position({
            x: '0%',
            y: '90%'
          })
        }

        // 添加笔记按钮 - 浮动操作按钮
        Button() {
          Image($r('app.media.add'))
            .width(24)
            .height(24)
            .fillColor('#ffffff')
        }
        .width(56)
        .height(56)
        .position({
          x: '85%',
          y: '85%'
        })
        .backgroundColor('#007AFF')
        .borderRadius(28)
        .shadow({
          radius: 8,
          color: '#40007AFF',
          offsetX: 0,
          offsetY: 2
        })
        .onClick(() => this.onAddNote())
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#ffffff')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#ffffff')
  }
}
复制代码
2. **编辑页面组件** - EditNote.ets
   - 笔记创建和编辑
   - 多类型内容管理
   - 文件附件支持
   - 自动保存机制
复制代码
/**
 * 笔记编辑页面 - 创建和编辑笔记的核心界面
 * 支持文本输入、文件附件添加、内容管理等功能
 */

import router from '@ohos.router';
import promptAction from '@ohos.promptAction';
import ConfirmationService from '../Service/ConfirmationService';
import { NoteData, NoteContentType, NoteContentItem } from '../model/NoteData';
import NoteService from '../Service/NoteService';
import FilePickerService, { FileInfo } from '../Service/FilePickerService';

@Entry
@Component
struct EditNote {
  // 状态管理
  @State title: string = '';                          // 笔记标题
  @State contentItems: NoteContentItem[] = [];        // 内容项数组
  @State isDirty: boolean = false;                    // 是否有未保存的修改
  @State currentTextContent: string = '';             // 当前编辑的文本内容
  @State isEditing: boolean = false;                  // 是否正在编辑(显示操作面板)
  private noteId: string = '';                        // 当前编辑的笔记ID(为空表示新建)

  /**
   * 页面即将显示时调用
   * 初始化数据和加载现有笔记(如果是编辑模式)
   */
  async aboutToAppear() {
    const params = router.getParams() as Record<string, string>;
    if (params && params.noteId) {
      this.noteId = params.noteId;
      await this.loadNote();
    }
  }

  // 原始数据备份,用于比较是否发生修改
  private originalTitle: string = '';
  private originalContentItems: NoteContentItem[] = [];

  /**
   * 检查笔记是否被修改
   * @returns 是否有真实修改
   */
  private checkIfModified(): boolean {
    // 检查标题是否修改
    if (this.title !== this.originalTitle) {
      return true;
    }
    
    // 检查内容项数量是否变化
    if (this.contentItems.length !== this.originalContentItems.length) {
      return true;
    }
    
    // 检查每个内容项是否变化
    for (let i = 0; i < this.contentItems.length; i++) {
      const currentItem = this.contentItems[i];
      const originalItem = this.originalContentItems[i];
      
      if (!originalItem || 
          currentItem.type !== originalItem.type ||
          currentItem.content !== originalItem.content) {
        return true;
      }
    }
    
    return false;
  }

  /**
   * 加载现有笔记数据
   */
  async loadNote() {
    const note = await NoteService.getNoteById(this.noteId);
    if (note) {
      this.title = note.title;
      this.contentItems = note.contentItems || [];
      // 保存原始数据用于比较
      this.originalTitle = note.title;
      this.originalContentItems = [...(note.contentItems || [])];
    }
  }

  /**
   * 返回按钮处理
   * 检查是否有未保存的修改并提示用户
   * @returns 是否阻止默认返回行为
   */
  onBackPress(): boolean {
    const hasRealChanges = this.checkIfModified();
    
    // 如果是查看现有笔记且没有真实修改,直接返回
    if (this.noteId && !hasRealChanges) {
      router.back();
      return false;
    }
    
    // 如果是新建笔记且有内容,或者现有笔记有真实修改,才显示确认弹窗
    if (hasRealChanges) {
      ConfirmationService.showCancelEditConfirmation(hasRealChanges).then((confirmed: boolean) => {
        if (confirmed) {
          router.back();
        }
      });
      return true; // 阻止默认返回行为
    } else {
      router.back();
    }
    return false;
  }

  /**
   * 保存笔记
   * 将当前编辑的笔记数据保存到持久化存储
   */
  async saveNote() {
    // 如果有未保存的文本内容,先保存
    if (this.currentTextContent.trim()) {
      this.addTextContent(this.currentTextContent);
      this.currentTextContent = '';
    }

    const note: NoteData = {
      id: this.noteId || Date.now().toString(),
      title: this.title,
      contentItems: this.contentItems,
      createTime: new Date().toISOString()
    };
    
    const success = await NoteService.saveNote(note);
    if (success) {
      ConfirmationService.showSuccessToast('笔记已保存');
      this.isDirty = false;
      // 更新原始数据
      this.originalTitle = this.title;
      this.originalContentItems = [...this.contentItems];
    } else {
      ConfirmationService.showErrorToast('保存失败,请重试');
    }
  }

  /**
   * 添加文本内容
   * @param text - 文本内容
   */
  addTextContent(text: string) {
    if (text.trim()) {
      this.contentItems.push({
        type: NoteContentType.TEXT,
        content: text.trim(),
        timestamp: new Date().toISOString()
      });
      this.isDirty = true;
    }
  }

  /**
   * 添加图片
   * 调用文件选择器选择图片文件
   */
  async addImage() {
    const fileInfo = await FilePickerService.pickImage();
    if (fileInfo) {
      this.contentItems.push({
        type: NoteContentType.IMAGE,
        content: fileInfo.uri,
        metadata: {
          name: fileInfo.name,
          size: fileInfo.size
        },
        timestamp: new Date().toISOString()
      });
      this.isDirty = true;
      ConfirmationService.showSuccessToast(`图片已添加: ${fileInfo.name}`);
    }
  }

  /**
   * 添加文档
   * 调用文件选择器选择文档文件
   */
  async addDocument() {
    const fileInfo = await FilePickerService.pickDocument();
    if (fileInfo) {
      this.contentItems.push({
        type: NoteContentType.DOCUMENT,
        content: fileInfo.uri,
        metadata: {
          name: fileInfo.name,
          size: fileInfo.size,
          type: fileInfo.type
        },
        timestamp: new Date().toISOString()
      });
      this.isDirty = true;
      ConfirmationService.showSuccessToast(`文档已添加: ${fileInfo.name}`);
    }
  }

  /**
   * 添加音频
   * 调用文件选择器选择音频文件
   */
  async addAudio() {
    const fileInfo = await FilePickerService.pickAudio();
    if (fileInfo) {
      this.contentItems.push({
        type: NoteContentType.AUDIO,
        content: fileInfo.uri,
        metadata: {
          name: fileInfo.name,
          size: fileInfo.size
        },
        timestamp: new Date().toISOString()
      });
      this.isDirty = true;
      ConfirmationService.showSuccessToast(`音频已添加: ${fileInfo.name}`);
    }
  }

  /**
   * 删除内容项
   * @param index - 内容项索引
   */
  async deleteContentItem(index: number) {
    if (index >= 0 && index < this.contentItems.length) {
      const item = this.contentItems[index];
      let contentType = '内容项';
      
      // 根据内容类型显示不同的确认信息
      switch (item.type) {
        case NoteContentType.TEXT:
          contentType = '文本内容';
          break;
        case NoteContentType.IMAGE:
          contentType = '图片';
          break;
        case NoteContentType.DOCUMENT:
          contentType = '文档';
          break;
        case NoteContentType.AUDIO:
          contentType = '音频';
          break;
      }
      
      const confirmed = await ConfirmationService.showContentDeleteConfirmation(contentType);
      
      if (confirmed) {
        this.contentItems.splice(index, 1);
        this.isDirty = true;
        
        ConfirmationService.showSuccessToast(`${contentType}已删除`);
      }
    }
  }

  /**
   * 内容项构建器
   * @param item - 内容项数据
   * @param index - 内容项索引
   * @returns 内容项UI组件
   */
  @Builder
  ContentItemBuilder(item: NoteContentItem, index: number) {
    Column() {
      if (item.type === NoteContentType.TEXT) {
        // 文本内容卡片
        Column() {
          Text(item.content)
            .fontSize(16)
            .fontColor('#333333')
            .lineHeight(24)
            .width('100%')
            .padding(16)
        }
        .width('100%')
        .backgroundColor('#ffffff')
        .borderRadius(12)
        .shadow({
          radius: 8,
          color: '#1a000000',
          offsetX: 0,
          offsetY: 2
        })
        .margin({ bottom: 12 })
      } else {
        // 文件内容卡片
        Row({
          space: 12
        }) {
          // 文件图标区域
          Column({
            space: 4
          }) {
            if (item.type === NoteContentType.IMAGE) {
              Image(item.content)
                .width(60)
                .height(60)
                .objectFit(ImageFit.Cover)
                .borderRadius(8)
            } else {
              Column() {
                Image(this.getFileTypeIcon(item.type))
                  .width(24)
                  .height(24)
                  .fillColor('#666666')
              }
              .width(60)
              .height(60)
              .backgroundColor('#f8f9fa')
              .borderRadius(8)
              .justifyContent(FlexAlign.Center)
            }
          }

          // 文件信息区域
          Column({
            space: 6
          }) {
            Text(this.getDisplayName(item))
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .fontColor('#333333')
              .width('100%')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
            
            Row({
              space: 8
            }) {
              Text(this.getFileTypeDescription(item.type))
                .fontSize(12)
                .fontColor('#666666')
                .backgroundColor('#f0f2f5')
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                .borderRadius(4)
              
              Text(`大小: ${this.formatFileSize(Number(item.metadata?.size) || 0)}`)
                .fontSize(12)
                .fontColor('#999999')
            }
          }
          .flexGrow(1)
          .justifyContent(FlexAlign.Center)
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#ffffff')
        .borderRadius(12)
        .shadow({
          radius: 8,
          color: '#1a000000',
          offsetX: 0,
          offsetY: 2
        })
        .margin({ bottom: 12 })
        .onClick(() => this.viewFile(item))
      }
    }
  }

  /**
   * 获取文件类型图标
   * @param type - 文件类型
   * @returns 图标资源
   */
  private getFileTypeIcon(type: NoteContentType): Resource {
    switch (type) {
      case NoteContentType.IMAGE:
        return $r('app.media.document');
      case NoteContentType.DOCUMENT:
        return $r('app.media.document');
      case NoteContentType.AUDIO:
        return $r('app.media.audio');
      default:
        return $r('app.media.document');
    }
  }

  /**
   * 获取显示名称
   * @param item - 内容项
   * @returns 显示名称
   */
  private getDisplayName(item: NoteContentItem): string {
    const nameValue = item.metadata?.name;
    let name = '未命名文件';
    
    if (typeof nameValue === 'string') {
      name = nameValue;
    } else if (typeof nameValue === 'number') {
      name = nameValue.toString();
    }
    
    // 移除文件扩展名
    name = name.replace(/\.[^/.]+$/, '');
    
    // 如果名称过长,截断显示
    if (name.length > 20) {
      name = name.substring(0, 20) + '...';
    }
    
    return name;
  }

  /**
   * 获取文件类型描述
   * @param type - 文件类型
   * @returns 类型描述
   */
  private getFileTypeDescription(type: NoteContentType): string {
    switch (type) {
      case NoteContentType.IMAGE:
        return '图片';
      case NoteContentType.DOCUMENT:
        return '文档';
      case NoteContentType.AUDIO:
        return '音频';
      default:
        return '文件';
    }
  }

  /**
   * 查看文件
   * @param item - 内容项
   */
  private viewFile(item: NoteContentItem) {
    if (item.type === NoteContentType.IMAGE || item.type === NoteContentType.DOCUMENT) {
      router.push({
        url: 'components/FileViewer',
        params: {
          fileUri: item.content,
          fileName: item.metadata?.name || '未命名文件'
        }
      });
    }
  }

  /**
   * 构建侧滑删除按钮
   * @param index - 内容项索引
   * @returns 侧滑按钮组件
   */
  @Builder
  getSwipeActionButtons(index: number) {
    Column() {
      Button() {
        Image($r('app.media.delete'))
          .width(18)
          .height(18)
          .fillColor('#ffffff')
      }
      .width(45)
      .height(45)
      .backgroundColor('#FF3B30')
      .borderRadius(8)
      .onClick(() => this.deleteContentItem(index))
    }
    .width(60)
    .height(60)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#FF3B30')
    .borderRadius(8)
  }

  /**
   * 格式化文件大小
   * @param bytes - 字节数
   * @returns 格式化后的文件大小
   */
  private formatFileSize(bytes: number): string {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }

  /**
   * 浮动操作按钮构建器
   * @returns 浮动按钮组件
   */
  @Builder
  FloatingActionButton() {
    Column() {
      Button() {
        Column() {
          Image($r('app.media.add'))
            .width(24)
            .height(24)
            .fillColor('#ffffff')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .width(56)
      .height(56)
      .backgroundColor('#007AFF')
      .borderRadius(28)
      .shadow({
        radius: 8,
        color: '#40007AFF',
        offsetX: 0,
        offsetY: 4
      })
      .onClick(() => {
        this.isEditing = !this.isEditing;
      })
    }
    .position({
      x: '85%',
      y: '85%'
    })
  }

  /**
   * 操作面板构建器
   * @returns 底部操作面板组件
   */
  @Builder
  ActionSheet() {
    Column() {
      // 标题
      Text('添加内容')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
        .width('100%')
        .textAlign(TextAlign.Center)
        .padding({ top: 20, bottom: 16 })
      
      // 操作按钮网格
      Row({
        space: 20
      }) {
        // 添加图片
        Column({
          space: 8
        }) {
          Button() {
            Image($r('app.media.document'))
              .width(32)
              .height(32)
              .fillColor('#007AFF')
          }
          .width(56)
          .height(56)
          .backgroundColor('#F0F7FF')
          .borderRadius(12)
          .onClick(() => {
            this.addImage();
            this.isEditing = false;
          })
          
          Text('图片')
            .fontSize(14)
            .fontColor('#333333')
        }
        .alignItems(HorizontalAlign.Center)
        .layoutWeight(1)

        // 添加文档
        Column({
          space: 8
        }) {
          Button() {
            Image($r('app.media.document'))
              .width(32)
              .height(32)
              .fillColor('#007AFF')
          }
          .width(56)
          .height(56)
          .backgroundColor('#F0F7FF')
          .borderRadius(12)
          .onClick(() => {
            this.addDocument();
            this.isEditing = false;
          })
          
          Text('文档')
            .fontSize(14)
            .fontColor('#333333')
        }
        .alignItems(HorizontalAlign.Center)
        .layoutWeight(1)

        // 添加音频
        Column({
          space: 8
        }) {
          Button() {
            Image($r('app.media.audio'))
              .width(32)
              .height(32)
              .fillColor('#007AFF')
          }
          .width(56)
          .height(56)
          .backgroundColor('#F0F7FF')
          .borderRadius(12)
          .onClick(() => {
            this.addAudio();
            this.isEditing = false;
          })
          
          Text('音频')
            .fontSize(14)
            .fontColor('#333333')
        }
        .alignItems(HorizontalAlign.Center)
        .layoutWeight(1)
      }
      .width('100%')
      .padding({ left: 24, right: 24, bottom: 20 })
      
      // 取消按钮
      Button('取消')
        .width('100%')
        .height(44)
        .fontSize(16)
        .fontColor('#007AFF')
        .fontWeight(FontWeight.Medium)
        .backgroundColor('transparent')
        .borderRadius(0)
        .margin({ top: 8 })
        .onClick(() => {
          this.isEditing = false;
        })
    }
    .width('100%')
    .backgroundColor('#ffffff')
    .borderRadius({ topLeft: 16, topRight: 16 })
    .shadow({
      radius: 16,
      color: '#40000000',
      offsetX: 0,
      offsetY: -2
    })
  }

  /**
   * 页面UI构建
   */
  build() {
    Stack() {
      // 主内容区域
      Column({
        space: 0
      }) {
        // 顶部导航栏 - 现代化设计
        Row({
          space: 16
        }) {
          // 返回按钮
          Button() {
          Image($r('app.media.back'))
              .width(24)
              .height(24)
              .fillColor('#333333')
          }
          .width(40)
          .height(40)
          .backgroundColor('transparent')
          .borderRadius(20)
          .onClick(() => this.onBackPress())

          // 页面标题
          Text(this.noteId ? '编辑笔记' : '新建笔记')
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')
            .flexGrow(1)
            .textAlign(TextAlign.Center)

          // 保存按钮
          Button('保存')
            .width(60)
            .height(36)
            .fontSize(14)
            .fontColor('#ffffff')
            .backgroundColor('#007AFF')
            .borderRadius(18)
            .onClick(async () => {
              await this.saveNote();
              router.back();
            })
        }
        .height(56)
        .width('100%')
        .padding({ left: 16, right: 16 })
        .backgroundColor('#ffffff')
        .shadow({
          radius: 4,
          color: '#0a000000',
          offsetX: 0,
          offsetY: 1
        })

        // 编辑区域
        Scroll() {
          Column({
            space: 0
          }) {
            // 标题输入区域
            Column() {
              Text('标题')
                .fontSize(14)
                .fontColor('#666666')
                .width('100%')
                .padding({left: 16, top: 16, bottom: 8 })
              
              TextInput({
                placeholder: '输入笔记标题...',
                text: this.title
              })
                .onChange((value: string) => {
                  this.title = value;
                  this.isDirty = true;
                })
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .fontColor('#333333')
                .placeholderColor('#999999')
                .width('100%')
                .padding({ left: 16, right: 16, bottom: 16 })
                .backgroundColor('#ffffff')
                .borderRadius(0)
            }

            // 文本内容区域
            Column() {
              Text('内容')
                .fontSize(14)
                .fontColor('#666666')
                .width('100%')
                .padding({ left: 16, top: 16, bottom: 8 })
              
              TextArea({
                placeholder: '输入笔记内容...',
                text: this.currentTextContent
              })
                .onChange((value: string) => {
                  this.currentTextContent = value;
                  this.isDirty = true;
                })
                .fontSize(16)
                .fontColor('#333333')
                .placeholderColor('#999999')
                .width('100%')
                .height(120)
                .padding({ left: 16, right: 16, bottom: 8 })
                .backgroundColor('#ffffff')
                .borderRadius(0)

              // 自动保存提示
              Text('内容会自动保存')
                .fontSize(12)
                .fontColor('#999999')
                .width('100%')
                .textAlign(TextAlign.End)
                .padding({ right: 16, bottom: 16 })
            }

            // 已添加内容区域
            if (this.contentItems.length > 0) {
              Column() {
                Text('已添加内容')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#333333')
                  .width('100%')
                  .padding({ left: 16, top: 24, bottom: 16 })

                List() {
                  ForEach(this.contentItems, (item: NoteContentItem, index: number) => {
                    ListItem() {
                      this.ContentItemBuilder(item, index)
                    }
                    .margin({ left: 16, right: 16, bottom: 8 })
                    .swipeAction({
                      end: this.getSwipeActionButtons(index)
                    })
                  }, (item: NoteContentItem, index: number) => index.toString())
                }
                .width('100%')
                .height(this.contentItems.length * 120)
                .layoutWeight(1)
              }
              .width('100%')
            }
          }
          .width('100%')
        }
        .flexGrow(1)
        .backgroundColor('#f8f9fa')
        .padding({ bottom:350 }) // 为浮动按钮留出空间
      }
      .width('100%')
      .height('100%')

      // 浮动操作按钮
      this.FloatingActionButton()

      // 底部操作面板
      if (this.isEditing) {
        Column() {
          Blank()
            .flexGrow(1)
            .onClick(() => {
              this.isEditing = false;
            })
          
          this.ActionSheet()
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#80000000')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8f9fa')
  }
}

6、入口能力配置

entry/src/main/ets/entryability/EntryAbility.ets

复制代码
/**
 * 入口能力类 - HarmonyOS应用的主入口点
 * 负责应用生命周期管理和初始化工作
 */

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import NoteService from '../Service/NoteService';

// 日志域标识
const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  /**
   * 能力创建时调用
   * @param want - 启动参数
   * @param launchParam - 启动配置
   */
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    try {
      // 设置应用颜色模式为系统默认
      this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
    }
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }

  /**
   * 能力销毁时调用
   * 清理资源和释放内存
   */
  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  /**
   * 窗口阶段创建时调用
   * 主要初始化工作和UI加载
   * @param windowStage - 窗口阶段对象
   */
  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    // 初始化笔记服务的上下文,为数据持久化做准备
    NoteService.setContext(this.context);

    // 加载主页面内容
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  /**
   * 窗口阶段销毁时调用
   * 清理UI相关资源
   */
  onWindowStageDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  /**
   * 应用进入前台时调用
   * 恢复应用状态和数据同步
   */
  onForeground(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  /**
   * 应用进入后台时调用
   * 保存状态和释放非必要资源
   */
  onBackground(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

以上就是开发指南的内容,如有问题欢迎指正

相关推荐
2501_920627616 小时前
Flutter 框架跨平台鸿蒙开发 - 派对策划助手应用
flutter·华为·harmonyos
@不误正业7 小时前
AI Agent多轮对话管理:3大架构源码级实现与性能对比(附鸿蒙实战)
人工智能·架构·harmonyos
里欧跑得慢7 小时前
Flutter 组件 powersync_core 的适配 鸿蒙Harmony 实战 - 驾驭极致离线优先架构、实现鸿蒙端高性能 SQL 增量同步与数据安全治理方案
flutter·harmonyos·鸿蒙·openharmony·powersync_core
轻口味7 小时前
HarmonyOS 6 自定义人脸识别模型9:基于tflite的人脸识别模型转换
华为·harmonyos
芙莉莲教你写代码7 小时前
Flutter 框架跨平台鸿蒙开发 - 网络安全学习应用
学习·web安全·flutter·华为·harmonyos
互联网散修8 小时前
零基础鸿蒙应用开发第二十五节:接口的行为契约能力
华为·harmonyos
@不误正业9 小时前
AI Agent工具调用深度实战-从Function-Calling到鸿蒙设备控制全链路
人工智能·华为·harmonyos
云和数据.ChenGuang10 小时前
鸿蒙应用对接扣子智能体:从 0 到 1 落地 AI 智能体集成
人工智能·华为·harmonyos
HwJack2011 小时前
HarmonyOS开发中ArkTS @Styles装饰器:告别复制粘贴,拥抱优雅的样式复用
华为·harmonyos