数据网格应用


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

适配的第三方库地址:

  • shared_preferences: https://pub.dev/packages/shared_preferences
  • file_selector: https://pub.dev/packages/file_selector
  • path_provider: https://pub.dev/packages/path_provider
  • intl: https://pub.dev/packages/intl

一、项目概述

运行效果图

image-20260412155412685

image-20260412155424787

image-20260412155431334

image-20260412155435206

image-20260412155439600

1.1 应用简介

数据网格应用是一款专业的数据管理工具,为用户提供高效的数据展示、筛选、编辑和导出功能。应用采用现代化的表格界面设计,支持多列数据展示、点击列头排序、多条件筛选、单元格编辑等核心功能,满足用户日常数据管理需求。

应用以清新的青绿色为主色调,象征数据的专业与可靠。涵盖数据网格、数据筛选、数据统计、系统设置四大模块。用户可以快速浏览数据、筛选目标记录、编辑数据内容、导出CSV文件,实现数据的高效管理。

1.2 核心功能

功能模块 功能描述 实现方式
数据展示 多列数据表格展示 DataTable
数据排序 点击列头升序/降序排序 排序算法
数据筛选 多条件组合筛选 筛选引擎
数据编辑 点击单元格编辑数据 TextField
数据导出 导出为CSV文件 文件写入
数据导入 导入CSV文件 文件解析
数据统计 数据汇总统计分析 统计引擎
数据持久化 本地数据存储 SharedPreferences

1.3 筛选类型定义

序号 筛选类型 Emoji 描述 适用数据
1 包含 🔍 包含指定文本 文本/数字
2 等于 ⚖️ 完全匹配 文本/数字
3 开头为 🔤 以指定文本开头 文本
4 结尾为 📝 以指定文本结尾 文本
5 大于 ⬆️ 数值大于指定值 数字
6 小于 ⬇️ 数值小于指定值 数字

1.4 排序方向定义

序号 排序方向 Emoji 描述 图标
1 升序 ⬆️ 从小到大排列 arrow_upward
2 降序 ⬇️ 从大到小排列 arrow_downward

1.5 数据列定义

序号 列名 数据类型 可编辑 宽度
1 ID 数字 80
2 姓名 文本 100
3 部门 文本 120
4 职位 文本 120
5 薪资 数字 100
6 入职日期 日期 110
7 状态 文本 80
8 邮箱 文本 180
9 电话 文本 120

1.6 技术栈

技术领域 技术选型 版本要求
开发框架 Flutter >= 3.0.0
编程语言 Dart >= 2.17.0
设计规范 Material Design 3 -
数据存储 SharedPreferences >= 2.0.0
文件选择 file_selector >= 1.0.0
文件路径 path_provider >= 2.0.0
日期格式 intl >= 0.18.0
目标平台 鸿蒙OS / Web API 21+

1.7 项目结构

lib/
└── main_data_grid.dart
    ├── DataGridApp                      # 应用入口
    ├── SortDirection                    # 排序方向枚举
    ├── FilterType                       # 筛选类型枚举
    ├── GridColumn                       # 网格列模型
    ├── GridRow                          # 网格行模型
    ├── DataGridHomePage                 # 主页面(底部导航)
    ├── _buildGridPage                   # 数据网格页
    ├── _buildFilterPage                 # 数据筛选页
    ├── _buildStatsPage                  # 数据统计页
    └── _buildSettingsPage               # 设置页

二、系统架构

2.1 整体架构图

Data Layer

Business Layer

Presentation Layer

主页面
DataGridHomePage

数据网格页

数据筛选页

数据统计页

设置页

数据表格

搜索栏

操作按钮

筛选条件

筛选结果

统计卡片

部门分布

关于应用

数据管理

数据管理器
DataManager

筛选引擎
FilterEngine

排序引擎
SortEngine

统计引擎
StatsEngine

GridColumn
网格列

GridRow
网格行

SharedPreferences
本地存储

2.2 类图设计

uses

uses

manages

manages

DataGridApp

+Widget build()

«enumeration»

SortDirection

+ascending

+descending

«enumeration»

FilterType

+String label

+contains()

+equals()

+startsWith()

+endsWith()

+greaterThan()

+lessThan()

GridColumn

+String key

+String label

+bool isNumeric

+bool isEditable

+double width

GridRow

+String id

+Map<String,dynamic> values

+DateTime createdAt

+DateTime? updatedAt

+copyWith()

DataGridHomePage

-int _currentIndex

-List<GridColumn> _columns

-List<GridRow> _rows

-List<GridRow> _filteredRows

-String? _sortColumn

-SortDirection _sortDirection

-Map<String,String> _filters

-Set<String> _selectedRows

+initState()

+_loadData()

+_saveData()

+_applyFiltersAndSort()

+_sort()

+_addRow()

+_deleteSelectedRows()

+_exportData()

+_importData()

2.3 页面导航流程

点击筛选Tab

点击统计Tab

点击设置Tab

添加数据

选择行

导出数据

导入数据

应用启动

数据网格页

用户操作

数据筛选页

设置筛选条件

应用筛选

数据统计页

查看统计数据

设置页

管理数据

新增行

编辑数据

选中数据

删除选中

导出CSV

保存文件

选择文件

解析CSV

2.4 数据操作流程

数据存储 排序引擎 筛选引擎 数据网格 用户 数据存储 排序引擎 筛选引擎 数据网格 用户 打开应用 加载数据 返回数据 输入搜索关键词 应用搜索筛选 返回筛选结果 点击列头排序 执行排序 返回排序结果 编辑单元格 更新数据 保存数据 导出数据 生成CSV 保存文件

三、核心模块设计

3.1 数据模型设计

3.1.1 筛选类型枚举 (FilterType)
enum FilterType {
  contains(label: '包含'),
  equals(label: '等于'),
  startsWith(label: '开头为'),
  endsWith(label: '结尾为'),
  greaterThan(label: '大于'),
  lessThan(label: '小于');

  final String label;
  const FilterType({required this.label});
}
3.1.2 排序方向枚举 (SortDirection)
enum SortDirection {
  ascending,
  descending,
}
3.1.3 网格列模型 (GridColumn)
class GridColumn {
  final String key;
  final String label;
  final bool isNumeric;
  final bool isEditable;
  final double width;

  const GridColumn({
    required this.key,
    required this.label,
    this.isNumeric = false,
    this.isEditable = true,
    this.width = 120,
  });
}
3.1.4 网格行模型 (GridRow)
class GridRow {
  final String id;
  final Map<String, dynamic> values;
  final DateTime createdAt;
  final DateTime? updatedAt;

  const GridRow({
    required this.id,
    required this.values,
    required this.createdAt,
    this.updatedAt,
  });

  GridRow copyWith({
    Map<String, dynamic>? values,
    DateTime? updatedAt,
  }) {
    return GridRow(
      id: id,
      values: values ?? this.values,
      createdAt: createdAt,
      updatedAt: updatedAt ?? this.updatedAt,
    );
  }
}
3.1.5 筛选类型分布
40% 20% 15% 10% 10% 5% 筛选类型使用分布示例 包含 等于 开头为 结尾为 大于 小于

3.2 页面结构设计

3.2.1 主页面布局

DataGridHomePage

IndexedStack

数据网格页

数据筛选页

数据统计页

设置页

NavigationBar

数据 Tab

筛选 Tab

统计 Tab

设置 Tab

3.2.2 数据网格页结构

数据网格页

顶部栏

选择提示栏

数据表格

标题

操作按钮

搜索框

添加行

删除选中

导出数据

导入数据

列头

数据行

单元格

3.2.3 数据筛选页结构

数据筛选页

SliverAppBar

筛选条件卡片

筛选结果卡片

字段筛选器

筛选类型选择

筛选值输入

清除按钮

结果数量

数据预览

3.2.4 数据统计页结构

数据统计页

SliverAppBar

统计卡片组

部门分布卡片

总记录数

总薪资

平均薪资

部门数

部门列表

进度条

百分比

3.3 筛选引擎逻辑

包含

等于

开头为

结尾为

大于

小于

开始筛选

复制数据列表

搜索关键词?

应用搜索筛选

遍历筛选条件

筛选值非空?

获取筛选类型

跳过此条件

筛选类型?

包含匹配

完全匹配

开头匹配

结尾匹配

数值比较

数值比较

过滤数据

还有条件?

返回结果

3.4 排序引擎逻辑

相同列

不同列

数字

文本

升序

降序

点击列头

当前排序列?

切换排序方向

设置新排序列

设为升序

应用排序

数据类型?

数值比较

字符串比较

比较大小

比较字符

排序方向?

正序排列

倒序排列

更新显示


四、UI设计规范

4.1 配色方案

应用以清新的青绿色为主色调,象征数据的专业与可靠:

颜色类型 色值 用途
主色 #00897B (Teal) 导航、主题元素
辅助色 #4DB6AC 数据表格
第三色 #80CBC4 筛选页面
强调色 #B2DFDB 统计页面
背景色 #FAFAFA 页面背景
卡片背景 #FFFFFF 信息卡片
数字颜色 #2196F3 数值显示
警告色 #FF9800 删除警告
成功色 #4CAF50 操作成功

4.2 统计卡片配色

统计项 色值 图标
总记录数 #2196F3 (Blue) table_rows
总薪资 #4CAF50 (Green) attach_money
平均薪资 #FF9800 (Orange) trending_up
部门数 #9C27B0 (Purple) business

4.3 字体规范

元素 字号 字重 颜色
页面标题 24px Bold 主色
表格标题 14px Bold #000000
表格内容 14px Regular #000000
数字内容 14px Regular #2196F3
统计数值 22px Bold 对应颜色
提示文字 14px Regular #666666

4.4 组件规范

4.4.1 数据表格
┌─────────────────────────────────────────────────────────────┐
│  数据网格应用              [+] [🗑] [⬇] [⬆]                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🔍 搜索数据...                                       │   │
│  └─────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│  ID ⬆ │ 姓名   │ 部门   │ 职位       │ 薪资    │ 状态    │
├─────────────────────────────────────────────────────────────┤
│  ☐ 1  │ 张三   │ 技术部 │ 高级工程师 │ 25000   │ 在职    │
│  ☑ 2  │ 李四   │ 产品部 │ 产品经理   │ 22000   │ 在职    │
│  ☐ 3  │ 王五   │ 技术部 │ 前端工程师 │ 18000   │ 在职    │
│  ☐ 4  │ 赵六   │ 市场部 │ 市场专员   │ 15000   │ 离职    │
└─────────────────────────────────────────────────────────────┘
4.4.2 筛选条件
┌─────────────────────────────────────────────────────────────┐
│  筛选条件                              [清除全部]            │
├─────────────────────────────────────────────────────────────┤
│  姓名     [包含 ▼]  [输入筛选值...        ]                  │
│  部门     [等于 ▼]  [技术部              ]                  │
│  薪资     [大于 ▼]  [15000               ]                  │
│  状态     [包含 ▼]  [在职                ]                  │
└─────────────────────────────────────────────────────────────┘
4.4.3 统计卡片
┌─────────────────────────────────────────────────────────────┐
│  ┌───────────────┐  ┌───────────────┐                      │
│  │  📊 8         │  │  💰 151000    │                      │
│  │  总记录数      │  │  总薪资        │                      │
│  └───────────────┘  └───────────────┘                      │
│  ┌───────────────┐  ┌───────────────┐                      │
│  │  📈 18888     │  │  🏢 5         │                      │
│  │  平均薪资      │  │  部门数        │                      │
│  └───────────────┘  └───────────────┘                      │
└─────────────────────────────────────────────────────────────┘
4.4.4 部门分布
┌─────────────────────────────────────────────────────────────┐
│  部门分布                                                    │
├─────────────────────────────────────────────────────────────┤
│  技术部          3 人 (37.5%)                                │
│  ═══════════════════════════════░░░░░░░░░░░░░░░░░░░░░░░░░   │
│                                                              │
│  产品部          2 人 (25.0%)                                │
│  ═════════════════════════░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   │
│                                                              │
│  市场部          1 人 (12.5%)                                │
│  ═══════════░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   │
└─────────────────────────────────────────────────────────────┘

五、核心功能实现

5.1 数据加载实现

Future<void> _loadData() async {
  final prefs = await SharedPreferences.getInstance();
  final dataJson = prefs.getString('grid_data');

  if (dataJson != null) {
    try {
      final List<dynamic> decoded = jsonDecode(dataJson);
      setState(() {
        _rows.clear();
        for (var item in decoded) {
          _rows.add(GridRow(
            id: item['id'] ?? '',
            values: Map<String, dynamic>.from(item['values'] ?? {}),
            createdAt: DateTime.tryParse(item['createdAt'] ?? '') ?? DateTime.now(),
            updatedAt: item['updatedAt'] != null
                ? DateTime.tryParse(item['updatedAt'])
                : null,
          ));
        }
        _applyFiltersAndSort();
      });
    } catch (e) {
      _loadSampleData();
    }
  } else {
    _loadSampleData();
  }
}

5.2 筛选引擎实现

void _applyFiltersAndSort() {
  List<GridRow> result = List.from(_rows);

  if (_searchQuery.isNotEmpty) {
    result = result.where((row) {
      return row.values.values.any((value) =>
          value.toString().toLowerCase().contains(_searchQuery.toLowerCase()));
    }).toList();
  }

  _filters.forEach((key, filterValue) {
    if (filterValue.isNotEmpty) {
      final filterType = _filterTypes[key] ?? FilterType.contains;
      result = result.where((row) {
        final cellValue = row.values[key]?.toString() ?? '';
        switch (filterType) {
          case FilterType.contains:
            return cellValue.toLowerCase().contains(filterValue.toLowerCase());
          case FilterType.equals:
            return cellValue.toLowerCase() == filterValue.toLowerCase();
          case FilterType.startsWith:
            return cellValue.toLowerCase().startsWith(filterValue.toLowerCase());
          case FilterType.endsWith:
            return cellValue.toLowerCase().endsWith(filterValue.toLowerCase());
          case FilterType.greaterThan:
            final numValue = num.tryParse(cellValue);
            final filterNum = num.tryParse(filterValue);
            return numValue != null && filterNum != null && numValue > filterNum;
          case FilterType.lessThan:
            final numValue = num.tryParse(cellValue);
            final filterNum = num.tryParse(filterValue);
            return numValue != null && filterNum != null && numValue < filterNum;
        }
      }).toList();
    }
  });

  if (_sortColumn != null) {
    result.sort((a, b) {
      final aValue = a.values[_sortColumn];
      final bValue = b.values[_sortColumn];
      int comparison = 0;
      if (aValue is num && bValue is num) {
        comparison = aValue.compareTo(bValue);
      } else {
        comparison = aValue.toString().compareTo(bValue.toString());
      }
      return _sortDirection == SortDirection.ascending ? comparison : -comparison;
    });
  }

  setState(() {
    _filteredRows.clear();
    _filteredRows.addAll(result);
  });
}

5.3 数据导出实现

Future<void> _exportData() async {
  try {
    final buffer = StringBuffer();
    buffer.writeln(_columns.map((c) => c.label).join(','));

    for (var row in _filteredRows) {
      buffer.writeln(_columns.map((c) {
        final value = row.values[c.key]?.toString() ?? '';
        return value.contains(',') ? '"$value"' : value;
      }).join(','));
    }

    final tempDir = await getTemporaryDirectory();
    final file = File('${tempDir.path}/data_export_${DateTime.now().millisecondsSinceEpoch}.csv');
    await file.writeAsString(buffer.toString());

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('数据已导出到: ${file.path}')),
      );
    }
  } catch (e) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('导出失败: $e')),
      );
    }
  }
}

5.4 数据导入实现

Future<void> _importData() async {
  try {
    final XFile? file = await openFile(acceptedTypeGroups: [
      XTypeGroup(label: 'CSV文件', extensions: ['csv']),
    ]);

    if (file == null) return;

    final content = await file.readAsString();
    final lines = content.split('\n');

    if (lines.isEmpty) return;

    final headers = lines[0].split(',').map((h) => h.trim().replaceAll('"', '')).toList();

    setState(() {
      _rows.clear();
      for (int i = 1; i < lines.length; i++) {
        if (lines[i].trim().isEmpty) continue;

        final values = <String, dynamic>{};
        final cells = lines[i].split(',');

        for (int j = 0; j < headers.length && j < cells.length; j++) {
          values[headers[j]] = cells[j].trim().replaceAll('"', '');
        }

        _rows.add(GridRow(
          id: (i).toString(),
          values: values,
          createdAt: DateTime.now(),
        ));
      }
      _applyFiltersAndSort();
    });

    _saveData();
  } catch (e) {
    // 处理错误
  }
}

5.5 统计计算实现

Widget _buildStatsPage() {
  final totalSalary = _filteredRows.fold<double>(0, (sum, row) {
    return sum + (num.tryParse(row.values['salary']?.toString() ?? '0') ?? 0);
  });
  final avgSalary = _filteredRows.isEmpty ? 0 : totalSalary / _filteredRows.length;

  final departmentStats = <String, int>{};
  for (var row in _filteredRows) {
    final dept = row.values['department']?.toString() ?? '未知';
    departmentStats[dept] = (departmentStats[dept] ?? 0) + 1;
  }
  // ... 渲染统计页面
}

六、交互设计

6.1 数据编辑流程

数据存储 编辑控制器 数据表格 用户 数据存储 编辑控制器 数据表格 用户 点击单元格 初始化编辑 显示输入框 输入新值 按回车确认 获取编辑值 更新行数据 保存数据 保存成功 显示新值

6.2 行选择流程

未选中

已选中

点击行选择框

当前状态?

添加到选中集合

从选中集合移除

显示选择提示栏

选中集合为空?

隐藏选择提示栏

更新选中计数

清空选中集合

6.3 数据导出流程

异常

点击导出按钮

生成CSV内容

写入临时文件

显示导出路径

导出失败

显示错误提示


七、扩展功能规划

7.1 后续版本规划

2024-01-07 2024-01-14 2024-01-21 2024-01-28 2024-02-04 2024-02-11 2024-02-18 2024-02-25 2024-03-03 2024-03-10 2024-03-17 2024-03-24 2024-03-31 2024-04-07 基础UI框架 数据表格实现 筛选排序功能 数据导入导出 数据统计功能 数据持久化 多表格支持 数据可视化 云端同步 V1.0 基础版本 V1.1 增强版本 V1.2 进阶版本 数据网格应用开发计划

7.2 功能扩展建议

7.2.1 多表格支持

扩展功能:

  • 创建多个数据表
  • 表格间数据关联
  • 表格切换管理
  • 表格模板保存
7.2.2 数据可视化

可视化功能:

  • 图表展示数据
  • 数据趋势分析
  • 自定义图表类型
  • 图表导出功能
7.2.3 云端同步

同步功能:

  • 数据云端备份
  • 多设备同步
  • 数据版本管理
  • 协作编辑功能

八、注意事项

8.1 开发注意事项

  1. 数据量控制:大数据量时需考虑分页加载

  2. 内存管理:及时释放不需要的数据和控制器

  3. 文件权限:导入导出需要文件读写权限

  4. 数据验证:导入数据时需验证格式正确性

  5. 异常处理:文件操作需完善的异常处理

8.2 常见问题

问题 原因 解决方案
数据加载失败 JSON格式错误 捕获异常并加载示例数据
导出文件乱码 编码问题 使用UTF-8编码
筛选无结果 条件过于严格 提示用户调整条件
排序异常 数据类型不一致 统一数据类型处理
导入失败 CSV格式错误 验证文件格式

8.3 使用技巧

📊 数据网格应用技巧 📊

数据管理

  • 定期导出数据备份
  • 使用筛选快速定位数据
  • 批量选择提高删除效率
  • 合理设置列宽便于查看

筛选技巧

  • 组合多个筛选条件
  • 使用数值比较筛选范围
  • 清除筛选恢复完整数据
  • 搜索框快速全文搜索

数据编辑

  • 点击单元格直接编辑
  • 数字列自动转换为数值
  • 编辑后自动保存
  • 支持撤销操作

九、运行说明

9.1 环境要求

环境 版本要求
Flutter SDK >= 3.0.0
Dart SDK >= 2.17.0
鸿蒙OS API 21+
Web浏览器 Chrome 90+

9.2 运行命令

# 查看可用设备
flutter devices

# 运行到Web服务器
flutter run -d web-server -t lib/main_data_grid.dart --web-port 8144

# 运行到鸿蒙设备
flutter run -d 127.0.0.1:5555 lib/main_data_grid.dart

# 代码分析
flutter analyze lib/main_data_grid.dart

十、总结

数据网格应用是一款专业的数据管理工具,为用户提供高效的数据展示、筛选、编辑和导出功能。应用支持多列数据展示、点击列头排序、多条件筛选、单元格编辑、CSV导入导出等核心功能,满足用户日常数据管理需求。

核心功能涵盖数据展示、数据排序、数据筛选、数据编辑、数据导出、数据导入、数据统计、数据持久化八大模块。用户可以快速浏览数据、筛选目标记录、编辑数据内容、导出CSV文件,实现数据的高效管理。

应用采用 Material Design 3 设计规范,以清新的青绿色为主色调,象征数据的专业与可靠。通过本应用,希望能够为用户提供便捷的数据管理体验,提升工作效率。

数据网格应用——高效管理您的数据


Logo

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

更多推荐