在这里插入图片描述
个人主页:ujainu

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在前两篇中,我们构建了一个功能完整的 内存版鸿蒙记事本:第一篇实现了基础的 CRUD 操作,第二篇加入了 实时全局搜索。然而,随着笔记数量持续增长,仅靠“标题+内容”和关键词搜索仍显不足——用户需要更精细、更结构化的组织方式。

此时,标签(Tags)系统 便成为提升信息架构清晰度的关键。通过为笔记打上如 #工作#灵感#购物清单 等标签,用户可按主题快速聚合相关内容。同时,结合 时间维度筛选(今日/本周/全部),我们能构建一个 多维、高效、直观 的笔记管理体系。

本文将为记事本引入 标签功能分类视图,支持:

  • 在编辑页添加/修改标签
  • 主界面顶部展示 可滚动标签筛选栏
  • 使用 分段按钮(SegmentedButton) 切换时间范围
  • 动态合并 搜索 + 标签 + 时间 三重过滤逻辑

最终,让每一条笔记都“各归其位”,真正实现 井井有条


一、为什么需要标签?—— 信息组织的升维

OpenHarmony 倡导“自然、高效”的用户体验,而 标签化管理 正是这一理念的体现:

组织方式 优点 缺点
纯列表 简单直接 信息杂乱,难定位
全局搜索 快速匹配关键词 依赖记忆,无法主动分类
标签 + 时间 主动分类 + 上下文感知 需额外输入,但收益巨大

标签的核心价值

  • 非互斥分类:一条笔记可同时属于“工作”和“会议”
  • 语义化聚合:点击 #灵感 即可查看所有创意片段
  • 降低认知负荷:无需记住具体内容,只需回忆主题

二、数据模型升级:Note 新增 tags 字段

首先,扩展 Note 类,支持标签列表:

class Note {
  final String id;
  String title;
  String content;
  final DateTime createdAt;
  List<String> tags; // 新增字段

  Note({
    required this.title,
    this.content = '',
    this.tags = const [], // 默认空列表
  })  : id = DateTime.now().microsecondsSinceEpoch.toString(),
        createdAt = DateTime.now();

  Note.withId({
    required this.id,
    required this.title,
    required this.content,
    required this.createdAt,
    required this.tags,
  });

  // 工具方法:判断是否包含某标签
  bool hasTag(String tag) => tags.contains(tag);
}

🔍 设计考量

  • tagsList<String>,便于存储与匹配
  • 提供 hasTag() 方法简化筛选逻辑
  • 构造函数兼容旧代码(默认空标签)

三、编辑页增强:标签输入区域

NoteEditorPage 中添加标签输入框。为符合鸿蒙简洁风格,我们采用 逗号分隔文本输入 + 自动解析 的方案:

class _NoteEditorPageState extends State<NoteEditorPage> {
  late final TextEditingController _titleController;
  late final TextEditingController _contentController;
  late final TextEditingController _tagsController; // 新增

  
  void initState() {
    super.initState();
    // ... 初始化 title 和 content
    if (widget.existingNote != null) {
      _tagsController = TextEditingController(
        text: widget.existingNote!.tags.join(', '), // 用逗号连接
      );
    } else {
      _tagsController = TextEditingController();
    }
  }

  
  void dispose() {
    // ... dispose 其他 controller
    _tagsController.dispose();
    super.dispose();
  }

  List<String> _parseTags(String input) {
    return input
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toSet() // 去重
        .toList();
  }

  void _saveNote() {
    final title = _titleController.text.trim();
    if (title.isEmpty) return;

    final tags = _parseTags(_tagsController.text);

    final note = widget.existingNote != null
        ? Note.withId(
            id: widget.existingNote!.id,
            title: title,
            content: _contentController.text.trim(),
            createdAt: widget.existingNote!.createdAt,
            tags: tags,
          )
        : Note(
            title: title,
            content: _contentController.text.trim(),
            tags: tags,
          );

    Navigator.pop(context, note);
  }
}

UI 部分:添加标签输入区


Widget build(BuildContext context) {
  return Scaffold(
    // ... AppBar
    body: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          // 标题 TextField
          const Divider(height: 24),
          // 内容 TextField
          const SizedBox(height: 16),
          TextField(
            controller: _tagsController,
            decoration: const InputDecoration(
              hintText: '标签(用逗号分隔,如:工作, 会议)',
              border: OutlineInputBorder(),
              focusedBorder: OutlineInputBorder(),
            ),
            maxLines: 1,
          ),
        ],
      ),
    ),
  );
}

交互优化

  • 用户输入 工作, 会议, 工作 → 自动去重为 [工作, 会议]
  • 提示文案明确格式要求
  • 使用标准 OutlineInputBorder 区分于标题/内容

四、主界面重构:多维筛选系统

现在,主界面需同时处理 搜索、标签、时间 三种筛选条件。

1. 状态变量定义

class _HomePageState extends State<HomePage> {
  final List<Note> _allNotes = [];
  List<Note> _filteredNotes = [];
  String _searchQuery = '';
  String? _selectedTag; // 当前选中的标签
  TimeFilter _timeFilter = TimeFilter.all; // 时间筛选枚举
  Set<String> _allTags = {}; // 所有唯一标签集合
  // ... 其他状态(FocusNode, Timer 等)
}

定义时间筛选枚举:

enum TimeFilter { all, today, thisWeek }

2. 标签筛选栏:可滚动 Chip 列表

使用 SingleChildScrollView + Row + FilterChip 实现:

Widget _buildTagFilterBar() {
  if (_allTags.isEmpty) return const SizedBox.shrink();

  return SingleChildScrollView(
    scrollDirection: Axis.horizontal,
    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    child: Row(
      children: [
        _buildFilterChip('全部', null, _selectedTag == null),
        ..._allTags.map((tag) {
          return _buildFilterChip(tag, tag, _selectedTag == tag);
        }).toList(),
      ],
    ),
  );
}

Widget _buildFilterChip(String label, String? value, bool isSelected) {
  return FilterChip(
    label: Text(label),
    selected: isSelected,
    onSelected: (selected) {
      setState(() {
        _selectedTag = selected ? value : null;
        _applyFilters(); // 重新应用所有筛选
      });
    },
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
  );
}

📌 鸿蒙风格适配

  • 圆角 16 符合 HarmonyOS Design
  • 水平滚动避免换行,节省垂直空间
  • “全部”选项始终置顶

3. 时间筛选:SegmentedButton(Material 3)

Widget _buildTimeFilter() {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    child: SegmentedButton<TimeFilter>(
      segments: const [
        ButtonSegment<TimeFilter>(value: TimeFilter.all, label: Text('全部')),
        ButtonSegment<TimeFilter>(value: TimeFilter.today, label: Text('今日')),
        ButtonSegment<TimeFilter>(value: TimeFilter.thisWeek, label: Text('本周')),
      ],
      selected: {_timeFilter},
      onSelectionChanged: (Set<TimeFilter> newSelection) {
        setState(() {
          _timeFilter = newSelection.first;
          _applyFilters();
        });
      },
      style: ButtonStyle(
        shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
      ),
    ),
  );
}

优势

  • SegmentedButton 是 Material 3 推荐组件,视觉现代
  • 单选模式天然契合时间筛选需求
  • 圆角 12 与卡片风格统一

五、核心逻辑:三重筛选合并

创建 _applyFilters() 方法,统一处理所有条件:

void _applyFilters() {
  List<Note> result = _allNotes;

  // 1. 时间筛选
  result = _filterByTime(result, _timeFilter);

  // 2. 标签筛选
  if (_selectedTag != null) {
    result = result.where((note) => note.hasTag(_selectedTag!)).toList();
  }

  // 3. 搜索筛选
  if (_searchQuery.isNotEmpty) {
    final lowerQuery = _searchQuery.toLowerCase();
    result = result.where((note) {
      return note.title.toLowerCase().contains(lowerQuery) ||
             note.content.toLowerCase().contains(lowerQuery) ||
             note.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
    }).toList();
  }

  setState(() {
    _filteredNotes = result;
  });
}

List<Note> _filterByTime(List<Note> notes, TimeFilter filter) {
  if (filter == TimeFilter.all) return notes;

  final now = DateTime.now();
  return notes.where((note) {
    if (filter == TimeFilter.today) {
      return note.createdAt.year == now.year &&
             note.createdAt.month == now.month &&
             note.createdAt.day == now.day;
    } else if (filter == TimeFilter.thisWeek) {
      final weekStart = now.subtract(Duration(days: now.weekday - 1));
      final weekEnd = weekStart.add(const Duration(days: 7));
      return note.createdAt.isAfter(weekStart) && note.createdAt.isBefore(weekEnd);
    }
    return true;
  }).toList();
}

⚠️ 关键细节

  • 搜索也覆盖标签内容(note.tags.any(...)
  • 时间计算考虑跨年/跨月边界
  • 所有过滤链式执行,顺序可调整

六、辅助逻辑:自动提取全局标签

每次 _allNotes 变更时,需更新 _allTags

void _updateAllTags() {
  final tags = <String>{};
  for (final note in _allNotes) {
    tags.addAll(note.tags);
  }
  setState(() {
    _allTags = tags;
  });
}

// 在 CRUD 操作后调用
void _onAddNote() {
  // ... 保存后
  _updateAllTags();
  _applyFilters();
}

七、完整可运行代码(Flutter + OpenHarmony)

以下为整合 CRUD + 搜索 + 标签 + 时间筛选 的完整代码:

// main.dart - 支持标签与多维筛选的鸿蒙记事本
import 'package:flutter/material.dart';
import 'dart:async';

// ==================== 数据模型 ====================
class Note {
  final String id;
  String title;
  String content;
  final DateTime createdAt;
  List<String> tags;

  Note({
    required this.title,
    this.content = '',
    this.tags = const [],
  })  : id = DateTime.now().microsecondsSinceEpoch.toString(),
        createdAt = DateTime.now();

  Note.withId({
    required this.id,
    required this.title,
    required this.content,
    required this.createdAt,
    required this.tags,
  });

  bool hasTag(String tag) => tags.contains(tag);
}

enum TimeFilter { all, today, thisWeek }

// ==================== 编辑页面 ====================
class NoteEditorPage extends StatefulWidget {
  final Note? existingNote;
  const NoteEditorPage({this.existingNote, super.key});

  
  State<NoteEditorPage> createState() => _NoteEditorPageState();
}

class _NoteEditorPageState extends State<NoteEditorPage> {
  late final TextEditingController _titleController;
  late final TextEditingController _contentController;
  late final TextEditingController _tagsController;

  
  void initState() {
    super.initState();
    if (widget.existingNote != null) {
      _titleController = TextEditingController(text: widget.existingNote!.title);
      _contentController = TextEditingController(text: widget.existingNote!.content);
      _tagsController = TextEditingController(text: widget.existingNote!.tags.join(', '));
    } else {
      _titleController = TextEditingController();
      _contentController = TextEditingController();
      _tagsController = TextEditingController();
    }
  }

  
  void dispose() {
    _titleController.dispose();
    _contentController.dispose();
    _tagsController.dispose();
    super.dispose();
  }

  List<String> _parseTags(String input) {
    return input
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toSet()
        .toList();
  }

  void _saveNote() {
    final title = _titleController.text.trim();
    if (title.isEmpty) return;
    final tags = _parseTags(_tagsController.text);
    final note = widget.existingNote != null
        ? Note.withId(
            id: widget.existingNote!.id,
            title: title,
            content: _contentController.text.trim(),
            createdAt: widget.existingNote!.createdAt,
            tags: tags,
          )
        : Note(title: title, content: _contentController.text.trim(), tags: tags);
    Navigator.pop(context, note);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.existingNote == null ? '新建笔记' : '编辑笔记'),
        backgroundColor: Colors.white,
        foregroundColor: Colors.black,
        elevation: 0,
        actions: [IconButton(icon: const Icon(Icons.save), onPressed: _saveNote)],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: _titleController,
              decoration: const InputDecoration(
                hintText: '标题',
                border: InputBorder.none,
                focusedBorder: InputBorder.none,
              ),
              style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              maxLines: 1,
            ),
            const Divider(height: 24),
            Expanded(
              child: TextField(
                controller: _contentController,
                decoration: const InputDecoration(
                  hintText: '写下你的想法...',
                  border: InputBorder.none,
                  focusedBorder: InputBorder.none,
                ),
                maxLines: null,
                keyboardType: TextInputType.multiline,
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _tagsController,
              decoration: const InputDecoration(
                hintText: '标签(用逗号分隔,如:工作, 会议)',
                border: OutlineInputBorder(),
                focusedBorder: OutlineInputBorder(),
              ),
              maxLines: 1,
            ),
          ],
        ),
      ),
    );
  }
}

// ==================== 主界面 ====================
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final List<Note> _allNotes = [];
  List<Note> _filteredNotes = [];
  String _searchQuery = '';
  String? _selectedTag;
  TimeFilter _timeFilter = TimeFilter.all;
  Set<String> _allTags = {};
  late FocusNode _searchFocusNode;
  bool _isSearching = false;
  Timer? _debounceTimer;

  
  void initState() {
    super.initState();
    _searchFocusNode = FocusNode();
    _filteredNotes = _allNotes;
  }

  
  void dispose() {
    _debounceTimer?.cancel();
    _searchFocusNode.dispose();
    super.dispose();
  }

  void _updateAllTags() {
    final tags = <String>{};
    for (final note in _allNotes) {
      tags.addAll(note.tags);
    }
    setState(() {
      _allTags = tags;
    });
  }

  void _applyFilters() {
    List<Note> result = _allNotes;
    result = _filterByTime(result, _timeFilter);
    if (_selectedTag != null) {
      result = result.where((note) => note.hasTag(_selectedTag!)).toList();
    }
    if (_searchQuery.isNotEmpty) {
      final lowerQuery = _searchQuery.toLowerCase();
      result = result.where((note) {
        return note.title.toLowerCase().contains(lowerQuery) ||
               note.content.toLowerCase().contains(lowerQuery) ||
               note.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
      }).toList();
    }
    setState(() {
      _filteredNotes = result;
    });
  }

  List<Note> _filterByTime(List<Note> notes, TimeFilter filter) {
    if (filter == TimeFilter.all) return notes;
    final now = DateTime.now();
    return notes.where((note) {
      if (filter == TimeFilter.today) {
        return note.createdAt.year == now.year &&
               note.createdAt.month == now.month &&
               note.createdAt.day == now.day;
      } else if (filter == TimeFilter.thisWeek) {
        final weekStart = now.subtract(Duration(days: now.weekday - 1));
        final weekEnd = weekStart.add(const Duration(days: 7));
        return note.createdAt.isAfter(weekStart) && note.createdAt.isBefore(weekEnd);
      }
      return true;
    }).toList();
  }

  void _enterSearchMode() {
    setState(() {
      _isSearching = true;
      _searchQuery = '';
    });
    _searchFocusNode.requestFocus();
    _applyFilters();
  }

  void _exitSearchMode() {
    setState(() {
      _isSearching = false;
      _searchQuery = '';
    });
    _searchFocusNode.unfocus();
    _applyFilters();
  }

  void _onSearchQueryChanged(String query) {
    _debounceTimer?.cancel();
    _debounceTimer = Timer(const Duration(milliseconds: 300), () {
      setState(() {
        _searchQuery = query.trim();
      });
      _applyFilters();
    });
  }

  void _onAddNote() {
    Navigator.push(context, MaterialPageRoute(builder: (_) => const NoteEditorPage()))
        .then((result) {
      if (result != null && result is Note) {
        setState(() {
          _allNotes.insert(0, result);
        });
        _updateAllTags();
        _applyFilters();
      }
    });
  }

  void _onEditNote(Note note) {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => NoteEditorPage(existingNote: note)),
    ).then((result) {
      if (result != null && result is Note) {
        setState(() {
          final index = _allNotes.indexWhere((n) => n.id == result.id);
          if (index != -1) {
            _allNotes[index] = result;
          }
        });
        _updateAllTags();
        _applyFilters();
      }
    });
  }

  void _onDeleteNote(Note note) {
    setState(() {
      _allNotes.removeWhere((n) => n.id == note.id);
    });
    _updateAllTags();
    _applyFilters();
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已删除 "${note.title}"')));
  }

  String _formatTime(DateTime time) {
    return '${time.year}-${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} '
        '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
  }

  Widget _buildNoteCard(Note note) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(note.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey),
                maxLines: 1, overflow: TextOverflow.ellipsis),
            const SizedBox(height: 8),
            if (note.content.isNotEmpty)
              Text(note.content, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(color: Colors.grey)),
            const SizedBox(height: 8),
            if (note.tags.isNotEmpty)
              Wrap(
                spacing: 6,
                runSpacing: 4,
                children: note.tags.map((tag) {
                  return Container(
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                    decoration: BoxDecoration(
                      color: Colors.blue.withOpacity(0.1),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text('#$tag', style: const TextStyle(color: Colors.blue, fontSize: 12)),
                  );
                }).toList(),
              ),
            const SizedBox(height: 8),
            Text(_formatTime(note.createdAt), style: const TextStyle(color: Colors.grey, fontSize: 12)),
          ],
        ),
      ),
    );
  }

  Widget _buildNoteItem(Note note, int index) {
    return Dismissible(
      key: Key(note.id),
      direction: DismissDirection.endToStart,
      background: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        child: const Icon(Icons.delete, color: Colors.white),
      ),
      onDismissed: (direction) => _onDeleteNote(note),
      child: GestureDetector(
        onTap: () => _onEditNote(note),
        child: _buildNoteCard(note),
      ),
    );
  }

  Widget _buildSearchField() {
    if (!_isSearching) {
      return const Text('我的笔记', style: TextStyle(fontWeight: FontWeight.w500));
    }
    return TextField(
      focusNode: _searchFocusNode,
      onChanged: _onSearchQueryChanged,
      decoration: InputDecoration(
        hintText: '搜索标题、内容或标签...',
        hintStyle: const TextStyle(color: Colors.grey),
        border: InputBorder.none,
        focusedBorder: InputBorder.none,
        prefixIcon: const Icon(Icons.search, color: Colors.grey),
        suffixIcon: _searchQuery.isNotEmpty
            ? IconButton(
                icon: const Icon(Icons.clear, size: 18),
                onPressed: () {
                  _onSearchQueryChanged('');
                  WidgetsBinding.instance.addPostFrameCallback((_) {
                    if (_searchFocusNode.hasFocus) {
                      _searchFocusNode.requestFocus();
                    }
                  });
                },
              )
            : null,
      ),
      style: const TextStyle(fontSize: 16),
    );
  }

  Widget _buildTagFilterBar() {
    if (_allTags.isEmpty) return const SizedBox.shrink();
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Row(
        children: [
          _buildFilterChip('全部', null, _selectedTag == null),
          ..._allTags.map((tag) {
            return _buildFilterChip(tag, tag, _selectedTag == tag);
          }).toList(),
        ],
      ),
    );
  }

  Widget _buildFilterChip(String label, String? value, bool isSelected) {
    return FilterChip(
      label: Text(label),
      selected: isSelected,
      onSelected: (selected) {
        setState(() {
          _selectedTag = selected ? value : null;
          _applyFilters();
        });
      },
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
    );
  }

  Widget _buildTimeFilter() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: SegmentedButton<TimeFilter>(
        segments: const [
          ButtonSegment<TimeFilter>(value: TimeFilter.all, label: Text('全部')),
          ButtonSegment<TimeFilter>(value: TimeFilter.today, label: Text('今日')),
          ButtonSegment<TimeFilter>(value: TimeFilter.thisWeek, label: Text('本周')),
        ],
        selected: {_timeFilter},
        onSelectionChanged: (Set<TimeFilter> newSelection) {
          setState(() {
            _timeFilter = newSelection.first;
            _applyFilters();
          });
        },
        style: ButtonStyle(
          shape: WidgetStatePropertyAll(RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
        ),
      ),
    );
  }

  Widget _buildNoteList() {
    if (_filteredNotes.isEmpty) {
      if (_searchQuery.isNotEmpty || _selectedTag != null) {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.search_off, size: 64, color: Colors.grey),
              const SizedBox(height: 16),
              const Text('未找到匹配的笔记', style: TextStyle(color: Colors.grey)),
            ],
          ),
        );
      } else {
        return const Center(child: Text('暂无笔记', style: TextStyle(color: Colors.grey)));
      }
    }
    return ListView.builder(
      padding: const EdgeInsets.only(top: 8),
      itemCount: _filteredNotes.length,
      itemBuilder: (context, index) => _buildNoteItem(_filteredNotes[index], index),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        foregroundColor: Colors.black,
        elevation: 0,
        title: _buildSearchField(),
        leading: _isSearching
            ? IconButton(icon: const Icon(Icons.arrow_back), onPressed: _exitSearchMode)
            : null,
        actions: !_isSearching
            ? [IconButton(icon: const Icon(Icons.search), onPressed: _enterSearchMode)]
            : null,
      ),
      body: Column(
        children: [
          if (!_isSearching) ...[
            _buildTimeFilter(),
            _buildTagFilterBar(),
          ],
          Expanded(child: _buildNoteList()),
        ],
      ),
      floatingActionButton: !_isSearching
          ? FloatingActionButton(onPressed: _onAddNote, child: const Icon(Icons.add))
          : null,
    );
  }
}

// ==================== 主程序入口 ====================
void main() {
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    theme: ThemeData(useMaterial3: true),
    home: HomePage(),
  ));
}

运行界面
在这里插入图片描述

结语

本文成功为鸿蒙记事本引入了 标签系统多维分类视图,通过 时间筛选 + 标签筛选 + 全局搜索 的三重组合,让用户在海量笔记中依然能 快速定位、高效管理。所有 UI 元素均严格遵循 OpenHarmony 的简洁、圆角、留白设计规范。

至此,我们的记事本已具备专业级信息组织能力。下一步,我们将把内存数据持久化到 本地数据库(Drift),确保笔记在 App 重启后依然安全留存。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐