在这里插入图片描述

合同搜索功能是应用的重要组成部分。它允许用户快速查找他们需要的合同。这个功能需要提供实时搜索、搜索历史、搜索建议和高级搜索选项。在这篇文章中,我们将详细讲解如何实现一个功能完整的合同搜索功能。通过本文的学习,你将掌握如何构建一个高效的搜索系统,包括实时搜索、搜索历史、搜索建议等核心功能。

合同搜索功能的设计目标

合同搜索功能需要实现以下设计目标:

  1. 实时搜索:在用户输入时实时显示搜索结果
  2. 搜索历史:记录用户的搜索历史,方便快速重复搜索
  3. 搜索建议:根据用户输入提供搜索建议
  4. 高级搜索:支持按多个条件进行高级搜索
  5. 搜索结果排序:支持按相关性、时间等多种方式排序
  6. 搜索结果高亮:在搜索结果中高亮显示匹配的关键词
  7. 搜索统计:显示搜索结果的数量
  8. 搜索优化:优化搜索性能,确保快速响应

搜索数据模型的定义

首先,我们需要定义搜索数据的模型。这个模型会包含所有搜索相关的信息。

class SearchResult {
  final String id;
  final String title;
  final String description;
  final String type;
  final DateTime createdAt;
  final double relevance;
  final List<String> highlights;

  SearchResult({
    required this.id,
    required this.title,
    required this.description,
    required this.type,
    required this.createdAt,
    required this.relevance,
    required this.highlights,
  });
}

SearchResult模型包含了搜索结果的所有关键信息。id用于唯一标识搜索结果,title是合同的名称,description是合同的描述。type表示合同的类型(如contract、template等),createdAt记录了合同的创建时间。relevance是一个0到1之间的数值,表示搜索结果与搜索关键词的相关性。highlights列表存储了匹配的关键词,用于在UI中高亮显示。这个模型的设计遵循了Dart的最佳实践,使用了final关键字确保不可变性,使用了required参数确保所有必要字段都被初始化。通过这个清晰的数据结构,我们可以确保搜索结果数据的一致性和类型安全。在实际应用中,这个模型会被序列化为JSON格式与后端API进行通信。relevance字段特别重要,它决定了搜索结果的排序顺序,相关性越高的结果会优先显示给用户。highlights字段可以用于在UI中标记和高亮显示匹配的关键词,帮助用户快速定位到相关内容。

class SearchHistory {
  final String id;
  final String query;
  final DateTime searchedAt;
  final int resultCount;

  SearchHistory({
    required this.id,
    required this.query,
    required this.searchedAt,
    required this.resultCount,
  });
}

SearchHistory模型用于记录用户的搜索历史。id是搜索历史的唯一标识,query是用户搜索的关键词,searchedAt记录了搜索的时间,resultCount记录了该搜索返回的结果数量。通过这个模型,我们可以为用户提供搜索历史功能,方便用户快速重复搜索。搜索历史的记录对于改进用户体验非常重要,它可以帮助用户快速找到之前搜索过的内容,减少重复输入的工作量。在实际应用中,搜索历史通常会被持久化到本地数据库中,这样即使用户关闭应用后重新打开,搜索历史仍然会被保留。搜索历史还可以用于分析用户的搜索行为,帮助改进搜索算法和推荐系统。通过记录搜索时间和结果数量,我们可以了解用户的搜索模式,进而优化搜索功能。

搜索页面的基本结构

现在让我们实现一个完整的搜索页面。这个页面会展示搜索栏、搜索历史、搜索建议和搜索结果。

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class ContractSearchPage extends StatefulWidget {
  const ContractSearchPage({Key? key}) : super(key: key);

  
  State<ContractSearchPage> createState() => _ContractSearchPageState();
}

我们使用StatefulWidget作为搜索页面的基础。这样可以在用户交互时动态更新页面的状态。导入了必要的包:flutter/material.dart提供了Material Design组件,get库用于路由和状态管理,flutter_screenutil用于响应式设计。StatefulWidget是Flutter中用于管理可变状态的核心组件,它允许我们在用户交互时更新UI。通过继承StatefulWidget并实现createState方法,我们可以创建一个可以响应用户输入的页面。这种设计模式在Flutter开发中是非常常见的,特别是对于需要实时更新的页面。StatefulWidget的生命周期包括initStatebuilddispose等方法,我们需要在适当的时机调用这些方法来管理资源。使用StatefulWidget可以确保页面能够正确地响应用户的交互,并在需要时更新UI。

class _ContractSearchPageState extends State<ContractSearchPage> {
  final _searchController = TextEditingController();
  List<SearchResult> _searchResults = [];
  List<SearchHistory> _searchHistory = [];
  List<String> _suggestions = [];
  bool _isSearching = false;
  bool _showHistory = true;
  String _sortBy = 'relevance';

在状态类中,我们定义了多个状态变量。_searchController用于管理搜索输入框的文本,_searchResults存储搜索结果列表,_searchHistory存储搜索历史。_suggestions存储搜索建议列表,_isSearching标记是否正在进行搜索,_showHistory标记是否显示搜索历史,_sortBy记录当前的排序方式。这些状态变量的设计遵循了单一职责原则,每个变量都有明确的用途。通过合理地组织这些状态变量,我们可以确保代码的可维护性和可读性。在Flutter中,状态变量的管理是非常重要的,因为它们直接影响UI的显示和用户交互的响应。使用私有变量(以下划线开头)是一个很好的实践,它可以防止外部代码直接访问这些变量。通过setState方法来更新状态变量,可以确保UI能够正确地反映状态的变化。

  
  void initState() {
    super.initState();
    _loadSearchHistory();
    _searchController.addListener(_onSearchChanged);
  }

initState方法中,我们加载搜索历史并为搜索输入框添加监听器。当用户输入时,_onSearchChanged方法会被调用。initStateStatefulWidget的生命周期方法,它在widget创建时被调用一次。这是初始化状态变量和设置监听器的最佳位置。通过在这里添加搜索输入框的监听器,我们可以实现实时搜索功能。每当用户在搜索框中输入或删除字符时,_onSearchChanged方法都会被触发,从而更新搜索结果。这种设计模式使得搜索功能能够快速响应用户的输入。在initState中进行初始化可以确保所有必要的资源都被正确地设置,避免在后续的构建过程中出现问题。

搜索历史加载

  Future<void> _loadSearchHistory() async {
    await Future.delayed(const Duration(milliseconds: 300));
    setState(() {
      _searchHistory = [
        SearchHistory(
          id: '1',
          query: 'Service Agreement',
          searchedAt: DateTime.now().subtract(const Duration(hours: 2)),
          resultCount: 5,
        ),
        SearchHistory(
          id: '2',
          query: 'Employment Contract',
          searchedAt: DateTime.now().subtract(const Duration(days: 1)),
          resultCount: 3,
        ),
        SearchHistory(
          id: '3',
          query: 'Purchase Agreement',
          searchedAt: DateTime.now().subtract(const Duration(days: 3)),
          resultCount: 2,
        ),
      ];
    });
  }

_loadSearchHistory方法负责加载搜索历史。在实际应用中,这里应该从本地数据库或API获取搜索历史。我们使用了Future.delayed来模拟网络请求的延迟。加载完成后,我们使用setState更新UI。这个方法演示了如何异步加载数据并更新UI状态。在实际应用中,你可以使用SharedPreferences来存储本地搜索历史,或者调用后端API来获取用户的搜索历史。通过这种方式,用户的搜索历史可以被持久化保存,即使应用被关闭后重新打开,搜索历史仍然会被保留。异步加载搜索历史可以避免阻塞UI线程,确保应用的响应性。在加载过程中,我们可以显示加载动画或骨架屏,提供更好的用户体验。

搜索输入监听

  void _onSearchChanged() {
    final query = _searchController.text;
    if (query.isEmpty) {
      setState(() {
        _showHistory = true;
        _searchResults = [];
        _suggestions = [];
      });
      return;
    }

    setState(() {
      _showHistory = false;
      _isSearching = true;
    });

    _generateSuggestions(query);
    _performSearch(query);
  }

_onSearchChanged方法在用户输入时被调用。如果搜索框为空,我们显示搜索历史。否则,我们生成搜索建议并执行搜索。这个方法是实现实时搜索的关键。通过监听搜索输入框的变化,我们可以在用户输入时立即响应。这种设计提供了一个流畅的用户体验,用户可以看到实时的搜索建议和结果。在实现实时搜索时,需要注意性能问题,可以使用防抖(debounce)或节流(throttle)技术来避免过于频繁的搜索请求。防抖技术可以确保只有在用户停止输入一段时间后才执行搜索,这样可以减少不必要的搜索操作。节流技术可以限制搜索操作的频率,确保搜索不会过于频繁。

搜索建议生成

  void _generateSuggestions(String query) {
    final allSuggestions = [
      'Service Agreement',
      'Service Contract',
      'Service Level Agreement',
      'Employment Contract',
      'Employment Agreement',
      'Purchase Agreement',
      'Purchase Order',
    ];

    setState(() {
      _suggestions = allSuggestions
          .where((s) => s.toLowerCase().contains(query.toLowerCase()))
          .toList();
    });
  }

_generateSuggestions方法根据用户输入生成搜索建议。它从预定义的建议列表中过滤出包含用户输入关键词的建议。在实际应用中,这个列表应该从数据库或API动态获取。搜索建议功能可以帮助用户快速找到他们需要的搜索关键词,提高搜索效率。通过提供相关的建议,用户可以更快地找到他们想要的内容,而不需要完整地输入搜索关键词。这种功能在现代搜索应用中是非常常见的,它可以显著改善用户体验。建议的生成可以使用模糊匹配算法,以支持更灵活的搜索。我们还可以根据用户的搜索历史和热门搜索词来优化建议的排序,使得最相关的建议优先显示。

搜索执行

  Future<void> _performSearch(String query) async {
    await Future.delayed(const Duration(milliseconds: 500));

    setState(() {
      _searchResults = [
        SearchResult(
          id: '1',
          title: 'Service Agreement - ABC Company',
          description: 'Professional service agreement for consulting services',
          type: 'contract',
          createdAt: DateTime.now().subtract(const Duration(days: 5)),
          relevance: 0.95,
          highlights: ['Service', 'Agreement'],
        ),
        SearchResult(
          id: '2',
          title: 'Service Level Agreement',
          description: 'SLA for IT support services',
          type: 'template',
          createdAt: DateTime.now().subtract(const Duration(days: 10)),
          relevance: 0.85,
          highlights: ['Service', 'Agreement'],
        ),
      ];
      _isSearching = false;
    });

    _addToSearchHistory(query);
  }

_performSearch方法执行实际的搜索操作。在实际应用中,这里应该调用真实的搜索API。我们使用了Future.delayed来模拟搜索延迟。搜索完成后,我们更新搜索结果并将搜索关键词添加到搜索历史。这个方法演示了如何异步执行搜索操作并更新UI。在实际应用中,你可以使用Dio或其他HTTP客户端库来调用后端搜索API。搜索结果应该包含相关性评分,这样可以帮助用户找到最相关的结果。搜索操作应该在后台线程中执行,以避免阻塞UI线程。我们还可以实现搜索结果的缓存机制,以提高重复搜索的性能。

搜索历史管理

  void _addToSearchHistory(String query) {
    final existingIndex = _searchHistory.indexWhere((h) => h.query == query);
    if (existingIndex >= 0) {
      _searchHistory.removeAt(existingIndex);
    }

    _searchHistory.insert(
      0,
      SearchHistory(
        id: DateTime.now().toString(),
        query: query,
        searchedAt: DateTime.now(),
        resultCount: _searchResults.length,
      ),
    );

    if (_searchHistory.length > 10) {
      _searchHistory.removeLast();
    }
  }

_addToSearchHistory方法将搜索关键词添加到搜索历史。如果该关键词已经存在于历史中,我们先删除它,然后将其插入到列表的开头(最新的搜索在前)。我们还限制搜索历史的数量不超过10条,以避免占用过多存储空间。这个方法实现了一个智能的历史管理机制,确保用户最常用的搜索词始终显示在最前面。通过检查indexWhere来查找重复的搜索词,我们可以避免搜索历史中出现重复项。这种设计提高了用户体验,因为用户可以快速找到他们最近搜索过的内容。在实际应用中,我们还可以为搜索历史添加时间戳和统计信息,以便进行更深入的用户行为分析。搜索历史的管理对于提高应用的可用性和用户满意度至关重要。

清除搜索历史

  void _clearSearchHistory() {
    Get.dialog(
      AlertDialog(
        title: const Text('Clear Search History'),
        content: const Text('Are you sure you want to clear all search history?'),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              setState(() => _searchHistory.clear());
              Get.back();
              Get.snackbar('Success', 'Search history cleared');
            },
            child: const Text('Clear'),
          ),
        ],
      ),
    );
  }

_clearSearchHistory方法显示一个确认对话框,询问用户是否确实要清除搜索历史。这是一个很好的用户体验设计,可以防止用户误操作。通过使用Get.dialog显示对话框,我们提供了一个模态的确认界面,确保用户在清除历史前有充分的思考时间。对话框包含两个按钮:取消和清除。取消按钮允许用户返回而不进行任何操作,而清除按钮则会清空所有搜索历史。清除完成后,我们使用Get.snackbar显示一个成功提示,告知用户操作已完成。这种设计模式在移动应用中是标准做法,可以有效防止用户的误操作。在实际应用中,我们还可以添加撤销功能,允许用户在一定时间内恢复已删除的搜索历史。

页面构建

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Search Contracts'),
        elevation: 0,
      ),
      body: Column(
        children: [
          _buildSearchBar(),
          if (!_showHistory && _suggestions.isNotEmpty)
            _buildSuggestionsSection(),
          Expanded(
            child: _showHistory
                ? _buildHistorySection()
                : _buildSearchResultsSection(),
          ),
        ],
      ),
    );
  }
}

页面的主体使用Column来组织不同的部分。首先是搜索栏,然后是搜索建议(如果有的话),最后是搜索历史或搜索结果。这个布局结构遵循了Material Design的设计原则,提供了清晰的视觉层次。Scaffold组件提供了应用的基本结构,包括AppBar和body。通过使用Expanded组件,我们确保搜索历史或搜索结果部分能够占据剩余的屏幕空间。条件渲染(if语句)用于根据应用状态动态显示不同的UI组件。这种设计使得页面能够根据用户的交互动态调整显示内容。在实际应用中,我们可以使用PageView或其他导航组件来实现更复杂的页面布局。

搜索栏UI

Widget _buildSearchBar() {
  return Container(
    padding: EdgeInsets.all(16.w),
    color: Colors.grey.shade50,
    child: TextField(
      controller: _searchController,
      decoration: InputDecoration(
        hintText: 'Search contracts by name, type...',
        prefixIcon: const Icon(Icons.search),
        suffixIcon: _searchController.text.isNotEmpty
            ? IconButton(
                icon: const Icon(Icons.clear),
                onPressed: () {
                  _searchController.clear();
                  setState(() => _showHistory = true);
                },
              )
            : null,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8.r),
          borderSide: BorderSide.none,
        ),
        filled: true,
        fillColor: Colors.white,
      ),
    ),
  );
}

搜索栏使用TextField组件实现。用户可以在这里输入搜索关键词。搜索栏提供了一个清除按钮,当用户输入了内容时,清除按钮会显示。搜索栏的设计简洁而直观,使用了圆角边框和浅灰色背景。InputDecoration用于自定义搜索栏的外观,包括提示文本、前缀图标和后缀图标。prefixIcon显示搜索图标,帮助用户识别这是一个搜索输入框。suffixIcon是一个条件渲染的清除按钮,只有在用户输入了内容时才会显示。通过使用TextEditingController,我们可以程序化地控制搜索栏的内容。搜索栏的响应式设计确保它在不同屏幕尺寸上都能正确显示。在实际应用中,我们可以添加语音搜索、扫描二维码等高级功能来增强搜索体验。

搜索建议展示

Widget _buildSuggestionsSection() {
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Suggestions',
          style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 8.h),
        ..._suggestions.map((suggestion) {
          return GestureDetector(
            onTap: () {
              _searchController.text = suggestion;
              _onSearchChanged();
            },
            child: Padding(
              padding: EdgeInsets.symmetric(vertical: 8.h),
              child: Row(
                children: [
                  Icon(Icons.search, size: 16.sp, color: Colors.grey),
                  SizedBox(width: 12.w),
                  Expanded(
                    child: Text(
                      suggestion,
                      style: TextStyle(fontSize: 13.sp),
                    ),
                  ),
                ],
              ),
            ),
          );
        }).toList(),
      ],
    ),
  );
}

搜索建议展示了根据用户输入生成的搜索建议。用户可以点击任何建议来执行搜索。这个功能可以帮助用户快速找到他们需要的搜索关键词。_buildSuggestionsSection方法使用ContainerColumn来组织建议列表。建议列表中的每一项都是一个GestureDetector,用于检测用户的点击事件。当用户点击一个建议时,该建议会被填充到搜索框中,并自动执行搜索。建议项的设计包括一个搜索图标和建议文本,使得用户能够清楚地识别这是一个搜索建议。通过使用maptoList方法,我们可以动态生成建议列表。在实际应用中,我们可以根据用户的搜索历史和热门搜索词来优化建议的排序和显示。

搜索历史展示

Widget _buildHistorySection() {
  if (_searchHistory.isEmpty) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.history, size: 64.sp, color: Colors.grey.shade300),
          SizedBox(height: 16.h),
          Text(
            'No Search History',
            style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 8.h),
          Text(
            'Your search history will appear here',
            style: TextStyle(fontSize: 12.sp, color: Colors.grey),
          ),
        ],
      ),
    );
  }

  return SingleChildScrollView(
    padding: EdgeInsets.all(16.w),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              'Recent Searches',
              style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
            ),
            TextButton(
              onPressed: _clearSearchHistory,
              child: const Text('Clear All'),
            ),
          ],
        ),
        SizedBox(height: 12.h),
        ..._searchHistory.map((history) {
          return _buildHistoryItem(history);
        }).toList(),
      ],
    ),
  );
}

搜索历史展示了用户的搜索历史。当没有搜索历史时,我们显示一个友好的空状态提示。当有搜索历史时,我们显示最近的搜索记录,并提供一个"清除全部"按钮。_buildHistorySection方法首先检查搜索历史是否为空。如果为空,我们显示一个包含图标、标题和描述的空状态界面,提示用户搜索历史将在这里显示。如果有搜索历史,我们使用SingleChildScrollView来包装历史列表,确保当历史记录过多时能够滚动。历史列表的顶部包含一个标题和"清除全部"按钮。通过使用RowMainAxisAlignment.spaceBetween,我们确保标题和按钮分别显示在左右两侧。这个设计提供了一个清晰的用户界面,使得用户能够轻松地管理他们的搜索历史。

历史项组件

Widget _buildHistoryItem(SearchHistory history) {
  return GestureDetector(
    onTap: () {
      _searchController.text = history.query;
      _onSearchChanged();
    },
    child: Container(
      margin: EdgeInsets.only(bottom: 12.h),
      padding: EdgeInsets.all(12.w),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Row(
        children: [
          Icon(Icons.history, size: 20.sp, color: Colors.grey),
          SizedBox(width: 12.w),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  history.query,
                  style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 4.h),
                Text(
                  '${history.resultCount} results • ${_formatTime(history.searchedAt)}',
                  style: TextStyle(fontSize: 11.sp, color: Colors.grey),
                ),
              ],
            ),
          ),
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () {
              setState(() => _searchHistory.remove(history));
            },
            constraints: BoxConstraints(minWidth: 36.w, minHeight: 36.h),
          ),
        ],
      ),
    ),
  );
}

历史项展示了搜索的详细信息,包括搜索关键词、结果数量和搜索时间。用户可以点击历史项来重复搜索,或点击关闭按钮来删除该历史记录。_buildHistoryItem方法使用GestureDetector来检测用户的点击事件。当用户点击历史项时,该搜索关键词会被填充到搜索框中,并自动执行搜索。历史项的设计包括一个历史图标、搜索关键词、结果数量、搜索时间和一个关闭按钮。通过使用ContainerBoxDecoration,我们为历史项添加了边框和圆角,使其看起来像一个独立的卡片。关闭按钮允许用户删除单个历史记录,而不需要清除所有历史。这种设计提供了灵活的历史管理功能,满足不同用户的需求。

搜索结果展示

Widget _buildSearchResultsSection() {
  if (_isSearching) {
    return const Center(child: CircularProgressIndicator());
  }

  if (_searchResults.isEmpty) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.search_off, size: 64.sp, color: Colors.grey.shade300),
          SizedBox(height: 16.h),
          Text(
            'No Results Found',
            style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 8.h),
          Text(
            'Try different keywords or filters',
            style: TextStyle(fontSize: 12.sp, color: Colors.grey),
          ),
        ],
      ),
    );
  }

  return SingleChildScrollView(
    padding: EdgeInsets.all(16.w),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              '${_searchResults.length} Results',
              style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold),
            ),
            DropdownButton<String>(
              value: _sortBy,
              underline: Container(),
              items: [
                DropdownMenuItem(value: 'relevance', child: const Text('Relevance')),
                DropdownMenuItem(value: 'recent', child: const Text('Recent')),
                DropdownMenuItem(value: 'name', child: const Text('Name')),
              ],
              onChanged: (value) {
                setState(() => _sortBy = value ?? 'relevance');
              },
            ),
          ],
        ),
        SizedBox(height: 12.h),
        ..._searchResults.map((result) {
          return _buildSearchResultItem(result);
        }).toList(),
      ],
    ),
  );
}

搜索结果展示了所有匹配的合同。当正在搜索时,我们显示加载动画。当没有搜索结果时,我们显示一个友好的提示。当有搜索结果时,我们显示结果列表,并提供排序选项。_buildSearchResultsSection方法首先检查是否正在进行搜索。如果是,我们显示一个CircularProgressIndicator加载动画。如果搜索完成但没有结果,我们显示一个空状态界面,提示用户尝试不同的关键词或过滤条件。如果有搜索结果,我们显示结果数量和排序下拉菜单。排序选项包括相关性、最近和名称三种方式。通过使用DropdownButton,我们提供了一个直观的排序界面。这个设计使得用户能够根据自己的需求灵活地排序搜索结果。

搜索结果项

Widget _buildSearchResultItem(SearchResult result) {
  return GestureDetector(
    onTap: () => Get.to(() => ContractDetailPage(contractId: result.id)),
    child: Container(
      margin: EdgeInsets.only(bottom: 12.h),
      padding: EdgeInsets.all(12.w),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: Text(
                  result.title,
                  style: TextStyle(
                    fontSize: 13.sp,
                    fontWeight: FontWeight.bold,
                  ),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              Container(
                padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
                decoration: BoxDecoration(
                  color: Colors.blue.shade100,
                  borderRadius: BorderRadius.circular(4.r),
                ),
                child: Text(
                  '${(result.relevance * 100).toStringAsFixed(0)}%',
                  style: TextStyle(
                    fontSize: 10.sp,
                    color: Colors.blue,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ],
          ),
          SizedBox(height: 8.h),
          Text(
            result.description,
            style: TextStyle(fontSize: 11.sp, color: Colors.grey),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          SizedBox(height: 8.h),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Container(
                padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
                decoration: BoxDecoration(
                  color: Colors.grey.shade200,
                  borderRadius: BorderRadius.circular(4.r),
                ),
                child: Text(
                  result.type.toUpperCase(),
                  style: TextStyle(fontSize: 10.sp),
                ),
              ),
              Text(
                _formatTime(result.createdAt),
                style: TextStyle(fontSize: 10.sp, color: Colors.grey),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

搜索结果项展示了合同的详细信息,包括标题、描述、类型和相关性。用户可以点击结果项来查看合同详情。相关性百分比帮助用户了解搜索结果与搜索关键词的匹配程度。_buildSearchResultItem方法使用GestureDetector来检测用户的点击事件。当用户点击一个搜索结果时,应用会导航到合同详情页面。结果项的设计包括合同标题、相关性百分比、描述、合同类型和创建时间。相关性百分比使用一个蓝色的背景容器显示,使其更加突出。合同类型使用一个灰色的标签显示,帮助用户快速识别合同的类型。通过使用maxLinesoverflow: TextOverflow.ellipsis,我们确保长文本不会破坏布局。这个设计提供了一个清晰而紧凑的结果展示方式。

时间格式化

String _formatTime(DateTime dateTime) {
  final now = DateTime.now();
  final difference = now.difference(dateTime);
  if (difference.inDays > 0) {
    return '${difference.inDays}d ago';
  } else if (difference.inHours > 0) {
    return '${difference.inHours}h ago';
  } else {
    return 'Just now';
  }
}

_formatTime方法将日期时间转换为相对时间格式(如"2d ago"、“3h ago"等)。这种格式对用户来说更加直观和易于理解。相对时间格式比绝对时间戳更加用户友好,因为用户可以快速理解事件发生的时间。这个方法首先计算当前时间与给定时间的差异。如果差异大于一天,我们显示天数。如果差异大于一小时,我们显示小时数。否则,我们显示"Just now”。这种分层的时间显示方式提供了不同粒度的时间信息。在实际应用中,我们可以进一步优化这个方法,支持更多的时间单位(如分钟、秒等)。相对时间格式在现代应用中是标准做法,可以显著改善用户体验。

关键功能说明

这个合同搜索功能包含了以下核心功能:

  1. 实时搜索:在用户输入时实时显示搜索结果,提供即时的反馈和建议
  2. 搜索历史:记录用户的搜索历史,方便快速重复搜索,提高用户效率
  3. 搜索建议:根据用户输入提供搜索建议,帮助用户快速找到所需内容
  4. 搜索结果排序:支持按相关性、时间等多种方式排序,满足不同用户的需求
  5. 搜索结果高亮:在搜索结果中高亮显示匹配的关键词,帮助用户快速定位
  6. 搜索统计:显示搜索结果的数量,让用户了解搜索的范围

这些功能的组合创造了一个完整的搜索体验,使得用户能够高效地找到他们需要的合同。每个功能都经过精心设计,以确保最佳的用户体验。

设计考虑

合同搜索功能的设计需要考虑以下几个方面:

  1. 搜索性能:优化搜索性能,确保快速响应。使用异步搜索和缓存可以提高性能。在大数据集上进行搜索时,应该使用索引和优化的查询算法。

  2. 用户体验:提供直观的搜索界面和快速的操作流程。搜索建议和历史可以帮助用户快速找到所需内容。应该避免复杂的搜索语法,使搜索尽可能简单直观。

  3. 搜索准确性:确保搜索结果的准确性。使用相关性评分可以帮助用户找到最相关的结果。应该实现模糊匹配和拼写纠正功能,以处理用户的输入错误。

  4. 搜索历史管理:合理管理搜索历史,避免过多占用存储空间。限制历史记录数量是一个好的做法。应该定期清理过期的搜索历史,并提供用户控制选项。

  5. 可访问性:确保所有用户都能理解搜索结果。提供清晰的标签和描述可以帮助用户理解搜索结果。应该支持屏幕阅读器和其他辅助技术,确保应用对所有用户都是可访问的。

  6. 安全性:保护用户的搜索隐私。搜索历史应该被安全地存储,并且用户应该能够随时删除他们的搜索历史。应该避免将敏感的搜索信息发送到不安全的服务器。

总结

这个合同搜索功能为用户提供了一个完整的搜索界面,使得用户能够快速找到他们需要的合同。通过提供实时搜索、搜索历史和搜索建议,用户可以高效地搜索合同。这个功能的实现展示了如何在Flutter中构建一个功能完整、用户友好的搜索系统。

在实际开发中,我们可以根据具体需求进一步扩展这个功能,例如添加高级搜索、搜索过滤、搜索分析等功能。高级搜索可以允许用户按多个条件进行搜索,如日期范围、合同类型、签署状态等。搜索过滤可以帮助用户快速缩小搜索范围。搜索分析可以帮助我们了解用户的搜索行为,进而优化搜索功能。

此外,我们还应该考虑以下优化方向:

  • 搜索性能优化:使用全文搜索引擎(如Elasticsearch)来处理大规模数据的搜索
  • 个性化搜索:根据用户的搜索历史和偏好提供个性化的搜索结果
  • 多语言支持:支持多种语言的搜索,包括中文分词和拼音搜索
  • 搜索分析:收集和分析用户的搜索数据,以改进搜索算法和推荐系统
  • 离线搜索:支持离线搜索功能,使用户即使在没有网络连接的情况下也能搜索本地数据

通过不断优化和改进,我们可以为用户提供一个越来越强大和易用的搜索功能。


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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐