更新概述

v1.14.0 版本为 OpenHarmony 钱包应用增加了强大的交易搜索和筛选功能。用户现在可以通过钱包页面的搜索按钮快速查找交易,支持按关键词、交易类型、分类等多维度筛选。这个新功能大大提高了用户查找历史交易的效率。

在这里插入图片描述


核心功能更新

1. 交易筛选模型

TransactionFilter 类定义
/// 交易搜索筛选模型
class TransactionFilter {
  final String? keyword;
  final String? category;
  final wallet.TransactionType? type;
  final DateTime? startDate;
  final DateTime? endDate;
  final double? minAmount;
  final double? maxAmount;

  TransactionFilter({
    this.keyword,
    this.category,
    this.type,
    this.startDate,
    this.endDate,
    this.minAmount,
    this.maxAmount,
  });

  /// 检查交易是否匹配筛选条件
  bool matches(wallet.Transaction transaction) {
    // 关键词匹配
    if (keyword != null && keyword!.isNotEmpty) {
      if (!transaction.title.toLowerCase().contains(keyword!.toLowerCase())) {
        return false;
      }
    }

    // 分类匹配
    if (category != null && category!.isNotEmpty) {
      if (transaction.category != category) {
        return false;
      }
    }

    // 类型匹配
    if (type != null && transaction.type != type) {
      return false;
    }

    // 日期范围匹配
    if (startDate != null && transaction.date.isBefore(startDate!)) {
      return false;
    }
    if (endDate != null && transaction.date.isAfter(endDate!.add(const Duration(days: 1)))) {
      return false;
    }

    // 金额范围匹配
    if (minAmount != null && transaction.amount < minAmount!) {
      return false;
    }
    if (maxAmount != null && transaction.amount > maxAmount!) {
      return false;
    }

    return true;
  }
}

说明

  • 支持关键词搜索(模糊匹配)
  • 支持分类筛选
  • 支持交易类型筛选(收入/支出)
  • 支持日期范围筛选
  • 支持金额范围筛选
筛选条件对比
条件 说明 示例
关键词 交易标题模糊匹配 “食物”、“工资”
分类 精确匹配分类 “食物”、“交通”
类型 收入或支出 收入、支出
日期范围 指定日期区间 2024-01-01 ~ 2024-12-31
金额范围 指定金额区间 100 ~ 1000

2. 交易搜索服务

TransactionSearchService 类
/// 交易搜索服务
class TransactionSearchService {
  /// 搜索交易
  static List<wallet.Transaction> search(
    List<wallet.Transaction> transactions,
    TransactionFilter filter,
  ) {
    return transactions.where((t) => filter.matches(t)).toList();
  }

  /// 获取所有分类
  static Set<String> getAllCategories(List<wallet.Transaction> transactions) {
    return transactions.map((t) => t.category).toSet();
  }

  /// 获取日期范围
  static DateTimeRange? getDateRange(List<wallet.Transaction> transactions) {
    if (transactions.isEmpty) return null;
    final sorted = transactions..sort((a, b) => a.date.compareTo(b.date));
    return DateTimeRange(start: sorted.first.date, end: sorted.last.date);
  }

  /// 获取金额范围
  static (double, double)? getAmountRange(List<wallet.Transaction> transactions) {
    if (transactions.isEmpty) return null;
    final amounts = transactions.map((t) => t.amount).toList();
    return (amounts.reduce((a, b) => a < b ? a : b), amounts.reduce((a, b) => a > b ? a : b));
  }
}

说明

  • search: 根据筛选条件搜索交易
  • getAllCategories: 获取所有分类列表
  • getDateRange: 获取交易的日期范围
  • getAmountRange: 获取交易的金额范围

3. 交易搜索页面

搜索栏
/// 构建搜索栏
Widget _buildSearchBar() {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: TextField(
      controller: _searchController,
      decoration: InputDecoration(
        hintText: '搜索交易标题...',
        prefixIcon: const Icon(Icons.search),
        suffixIcon: _searchController.text.isNotEmpty
            ? IconButton(
                icon: const Icon(Icons.clear),
                onPressed: () {
                  _searchController.clear();
                  setState(() {});
                },
              )
            : null,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
        ),
        contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ),
      onChanged: (value) {
        setState(() {});
      },
    ),
  );
}

说明

  • 实时搜索,输入时立即更新结果
  • 支持清空按钮快速清除搜索词
  • 圆角设计,美观易用

在这里插入图片描述

筛选条件
/// 构建筛选部分
Widget _buildFilterSection(Set<String> categories) {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '筛选条件',
            style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  fontWeight: FontWeight.bold,
                ),
          ),
          const SizedBox(height: 12),
          // 交易类型筛选
          Text('交易类型', style: Theme.of(context).textTheme.bodySmall),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: ChoiceChip(
                  label: const Text('全部'),
                  selected: _selectedType == null,
                  onSelected: (selected) {
                    setState(() {
                      _selectedType = null;
                    });
                  },
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: ChoiceChip(
                  label: const Text('收入'),
                  selected: _selectedType == wallet.TransactionType.income,
                  onSelected: (selected) {
                    setState(() {
                      _selectedType = selected ? wallet.TransactionType.income : null;
                    });
                  },
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: ChoiceChip(
                  label: const Text('支出'),
                  selected: _selectedType == wallet.TransactionType.expense,
                  onSelected: (selected) {
                    setState(() {
                      _selectedType = selected ? wallet.TransactionType.expense : null;
                    });
                  },
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          // 分类筛选
          Text('分类', style: Theme.of(context).textTheme.bodySmall),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              ChoiceChip(
                label: const Text('全部'),
                selected: _selectedCategory == null,
                onSelected: (selected) {
                  setState(() {
                    _selectedCategory = null;
                  });
                },
              ),
              ...categories.map((category) {
                return ChoiceChip(
                  label: Text(category),
                  selected: _selectedCategory == category,
                  onSelected: (selected) {
                    setState(() {
                      _selectedCategory = selected ? category : null;
                    });
                  },
                );
              }).toList(),
            ],
          ),
          const SizedBox(height: 16),
          // 重置按钮
          SizedBox(
            width: double.infinity,
            child: OutlinedButton.icon(
              icon: const Icon(Icons.refresh),
              label: const Text('重置筛选'),
              onPressed: () {
                setState(() {
                  _searchController.clear();
                  _selectedCategory = null;
                  _selectedType = null;
                });
              },
            ),
          ),
        ],
      ),
    ),
  );
}

说明

  • 交易类型:全部、收入、支出
  • 分类:动态生成,基于实际数据
  • 重置按钮:一键清除所有筛选条件
搜索结果
/// 构建结果部分
Widget _buildResultsSection(List<wallet.Transaction> results) {
  return Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              '搜索结果',
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
            ),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
              decoration: BoxDecoration(
                color: Colors.teal.shade100,
                borderRadius: BorderRadius.circular(20),
              ),
              child: Text(
                '${results.length} 条',
                style: TextStyle(
                  color: Colors.teal.shade700,
                  fontWeight: FontWeight.bold,
                  fontSize: 12,
                ),
              ),
            ),
          ],
        ),
        const SizedBox(height: 12),
        if (results.isEmpty)
          Container(
            padding: const EdgeInsets.all(32),
            decoration: BoxDecoration(
              color: Colors.grey.shade100,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Center(
              child: Column(
                children: [
                  Icon(Icons.search_off, size: 48, color: Colors.grey.shade400),
                  const SizedBox(height: 12),
                  Text('未找到匹配的交易'),
                ],
              ),
            ),
          )
        else
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: results.length,
            itemBuilder: (context, index) {
              final transaction = results[index];
              final isIncome = transaction.type == wallet.TransactionType.income;
              final color = isIncome ? Colors.green : Colors.red;

              return Container(
                margin: const EdgeInsets.only(bottom: 12),
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey.shade300),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Row(
                  children: [
                    Container(
                      width: 48,
                      height: 48,
                      decoration: BoxDecoration(
                        color: color.withOpacity(0.1),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Center(
                        child: Icon(
                          isIncome ? Icons.add_circle : Icons.remove_circle,
                          color: color,
                        ),
                      ),
                    ),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(transaction.title, style: const TextStyle(fontWeight: FontWeight.w600)),
                          const SizedBox(height: 4),
                          Row(
                            children: [
                              Container(
                                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                                decoration: BoxDecoration(
                                  color: Colors.grey.shade200,
                                  borderRadius: BorderRadius.circular(4),
                                ),
                                child: Text(transaction.category, style: const TextStyle(fontSize: 11)),
                              ),
                              const SizedBox(width: 8),
                              Text(
                                '${transaction.date.month}/${transaction.date.day}',
                                style: TextStyle(fontSize: 11, color: Colors.grey.shade600),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ),
                    Text(
                      '${isIncome ? '+' : '-'}¥${transaction.amount.toStringAsFixed(2)}',
                      style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: color),
                    ),
                  ],
                ),
              );
            },
          ),
      ],
    ),
  );
}

说明

  • 显示搜索结果数量
  • 空状态提示
  • 结果列表展示
  • 每条交易显示标题、分类、日期、金额

UI 变化

交易搜索页面布局

┌─────────────────────────────────┐
│  搜索交易                        │
├─────────────────────────────────┤
│  [🔍 搜索交易标题...] [✕]       │
├─────────────────────────────────┤
│  筛选条件                        │
│  交易类型: [全部] [收入] [支出]  │
│  分类: [全部] [食物] [交通] ...  │
│  [🔄 重置筛选]                   │
├─────────────────────────────────┤
│  搜索结果 (5 条)                 │
│  ┌─────────────────────────┐   │
│  │ 🟢 工资      工作  12/24 │   │
│  │    +¥5000                │   │
│  └─────────────────────────┘   │
│  ┌─────────────────────────┐   │
│  │ 🔴 午餐      食物  12/24 │   │
│  │    -¥50                  │   │
│  └─────────────────────────┘   │
└─────────────────────────────────┘

访问方式

  • 进入钱包页面
  • 点击 AppBar 右侧的搜索按钮(🔍)
  • 进入交易搜索页面

版本对比

功能 v1.13.0 v1.14.0
高级统计
趋势预测
财务概览
交易筛选模型
交易搜索服务
关键词搜索
多维度筛选
搜索结果展示
筛选条件重置

使用场景

场景 1:查找特定交易

  1. 进入钱包页面
  2. 点击搜索按钮
  3. 输入交易标题关键词(如"工资")
  4. 查看搜索结果

场景 2:按分类查看交易

  1. 进入搜索页面
  2. 在筛选条件中选择分类(如"食物")
  3. 查看该分类的所有交易

场景 3:查看收入或支出

  1. 进入搜索页面
  2. 在交易类型中选择"收入"或"支出"
  3. 查看对应类型的所有交易

场景 4:组合筛选

  1. 进入搜索页面
  2. 输入关键词
  3. 选择交易类型
  4. 选择分类
  5. 查看满足所有条件的交易

技术亮点

1. 灵活的筛选模型

  • 支持多个筛选条件
  • 条件可选,支持任意组合
  • 易于扩展新的筛选条件

2. 高效的搜索算法

  • 使用 where 进行链式筛选
  • 支持模糊匹配和精确匹配
  • 性能优化,适合大数据量

3. 实时搜索反馈

  • 输入时立即更新结果
  • 显示结果数量
  • 空状态提示

4. 用户友好的界面

  • 直观的筛选条件
  • 清晰的结果展示
  • 一键重置功能

下一步计划

v1.15.0 将继续增强功能,计划增加:

  • 📊 交易统计报告
  • 📅 日历视图
  • 🏷️ 标签快速筛选
  • 💾 收藏常用筛选

感谢使用 OpenHarmony 钱包! 🎉

如有建议或问题,欢迎反馈。

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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐