flutter_for_openharmony城市井盖地图app实战+批量导入实现

1. 这个功能解决什么问题
批量导入帮助管理员“快速导入”大量点位或工单数据,核心价值体现在以下维度:
- 效率提升:替代手动逐条录入,单次可处理成百上千条数据,大幅降低操作成本
- 流程闭环:覆盖“导入-校验-反馈-记录”全流程,确保数据导入可追溯、可管控
- 体验优化:通过进度可视化、状态提示,让用户清晰感知导入过程
- 兼容性预留:虽暂未接入文件选择插件,但UI层预留扩展接口,便于后续对接真实文件解析逻辑
本次实现聚焦四大核心环节:
- 导入说明:当前工程未引入文件选择插件,仅展示 UI 流程
- 进度模拟:模拟导入耗时与进度更新
- 状态管理:防止重复点击
- 用户反馈:导入完成提示
这个页面是典型的“进度+异步”组合,适合做 LinearProgressIndicator + Future.delayed 的最佳实践示例。
2. 相关文件一览
lib/feature_pages.dart(BatchImportPage):核心页面载体,包含UI渲染、异步逻辑、状态管理全量代码
3. 状态定义
批量导入页面的核心状态需满足两个基础诉求:
- 进度感知:用0~1的浮点数精准反映导入进度,支持百分比换算展示
- 操作管控:通过布尔值控制按钮可点击状态,避免重复触发导入流程
class _BatchImportPageState extends State<BatchImportPage> {
double _progress = 0.0;
bool _running = false;
}
状态设计的关键考量:
_progress初始值设为0.0,确保进度条初始状态为空_running默认为false,保证页面加载完成后按钮可正常点击- 两个状态均挂载在State类中,依托setState实现UI实时刷新
4. 导入说明卡片
导入说明卡片的设计目标是“清晰告知、降低误解”,核心设计要点:
- 视觉引导:用上传图标强化“导入”操作认知
- 信息透明:明确标注“仅展示UI流程”,避免用户误以为功能缺失
- 布局规范:采用Card+ListTile组合,贴合Material Design设计规范,保持与整体APP风格统一
Card(
child: ListTile(
leading: const Icon(Icons.file_upload_outlined),
title: const Text('导入来源'),
subtitle: const Text('当前工程未引入文件选择依赖;此处展示流程UI'),
),
)
细节优化说明:
- leading图标选用
Icons.file_upload_outlined(轮廓版),视觉更轻盈,符合移动端设计趋势 - subtitle文字简洁直白,无专业术语,降低用户理解成本
5. 进度卡片
进度卡片是用户感知导入过程的核心载体,设计逻辑拆解:
- 信息层级:进度百分比文本在上,进度条居中,操作按钮在下,符合用户视觉浏览顺序
- 交互管控:按钮状态与
_running绑定,运行时自动禁用,从源头避免重复操作 - 空间留白:通过SizedBox设置合理间距,避免元素拥挤,提升视觉舒适度
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('进度: ${(100 * _progress).round()}%'),
const SizedBox(height: 10),
LinearProgressIndicator(value: _progress),
],
),
),
)
进度展示的关键细节:
- 百分比计算用
round()取整,避免小数位过多导致视觉杂乱 - LinearProgressIndicator不设置固定高度,使用默认样式保证兼容性
- Padding设为12px,与Card组件的内边距规范保持一致
const SizedBox(height: 12),
FilledButton(
onPressed: _running ? null : _start,
child: Text(_running ? '导入中...' : '开始导入'),
),
按钮交互设计要点:
- 按钮文本动态切换,直观反馈当前操作状态
- FilledButton为Material 3新增组件,相比RaisedButton视觉层级更清晰
- onPressed回调通过三元表达式简化逻辑,避免额外的条件判断代码
6. 导入逻辑
导入核心逻辑需兼顾“流程模拟”与“异常防护”,核心设计点:
- 分步执行:将导入拆分为20个步骤,每步延时120ms,模拟真实的文件解析、数据校验、入库流程
- 生命周期防护:每次更新状态前检查
mounted,避免组件销毁后触发setState导致崩溃 - 反馈闭环:导入完成后通过SnackBar给出明确提示,完成用户操作闭环
Future<void> _start() async {
setState(() {
_running = true;
_progress = 0.0;
});
for (var i = 0; i < 20; i++) {
await Future<void>.delayed(const Duration(milliseconds: 120));
if (!mounted) return;
初始化与循环设计说明:
- 启动时重置进度为0.0,确保每次导入都是全新的进度计数
- 循环次数设为20次,兼顾“进度粒度”与“总耗时”(20*120ms=2.4秒),符合用户等待心理阈值
- Future.delayed选用milliseconds级别的延时,模拟更细腻的处理过程
setState(() => _progress = (i + 1) / 20.0);
}
if (!mounted) return;
setState(() => _running = false);
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('导入完成(模拟)')));
}
收尾逻辑设计要点:
- 循环结束后再次检查mounted,防止循环过程中组件被销毁
- 重置
_running为false,恢复按钮可点击状态 - SnackBar提示文字标注“模拟”,与导入说明保持信息一致
7. 完整页面代码(工程真实内容)
完整页面采用“状态管理+UI渲染”分离的设计思路,核心结构拆解:
- 页面结构:StatefulWidget承载可变状态,符合Flutter“数据驱动UI”的核心思想
- 方法封装:导入逻辑封装为
_start方法,使build方法更简洁,聚焦UI渲染 - 布局设计:ListView作为根布局,适配小屏设备,避免内容溢出
class BatchImportPage extends StatefulWidget {
const BatchImportPage({super.key});
State<BatchImportPage> createState() => _BatchImportPageState();
}
StatefulWidget设计说明:
- 构造方法添加
super.key,符合Flutter 3.0+的最佳实践 - 独立的State类拆分,使状态管理逻辑与页面配置分离
class _BatchImportPageState extends State<BatchImportPage> {
double _progress = 0.0;
bool _running = false;
Future<void> _start() async {
setState(() {
_running = true;
_progress = 0.0;
});
状态与方法初始化说明:
- 状态变量声明在State类顶部,便于集中管理和查阅
_start方法标记为async,支持异步操作,符合导入流程的耗时特性
for (var i = 0; i < 20; i++) {
await Future<void>.delayed(const Duration(milliseconds: 120));
if (!mounted) return;
setState(() => _progress = (i + 1) / 20.0);
}
循环逻辑优化点:
- 每次循环仅更新进度值,逻辑单一职责,便于维护
- 进度计算采用
(i+1)/20.0,确保最终进度为1.0(100%)
if (!mounted) return;
setState(() => _running = false);
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('导入完成(模拟)')));
}
收尾逻辑说明:
- SnackBar通过
ScaffoldMessenger调用,替代旧版Scaffold.of(context),兼容最新Flutter版本 - 提示文本简洁明了,用户可快速感知操作结果
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('批量导入(模拟)')),
body: ListView(
padding: const EdgeInsets.all(12),
children: [
页面布局核心设计:
- Scaffold作为根布局,提供AppBar、body等标准结构
- ListView的padding设为12px,与内部Card组件的间距形成呼应
- AppBar标题标注“模拟”,与功能说明保持一致
Card(
child: ListTile(
leading: const Icon(Icons.file_upload_outlined),
title: const Text('导入来源'),
subtitle: const Text('当前工程未引入文件选择依赖;此处展示流程UI'),
),
),
导入说明卡片复用:
- 直接复用前文定义的Card组件,保证代码一致性
- ListTile的subtitle明确说明功能状态,降低用户疑惑
const SizedBox(height: 12),
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('进度: ${(100 * _progress).round()}%'),
进度卡片布局说明:
- CrossAxisAlignment设为start,使文本左对齐,符合阅读习惯
- 进度百分比通过计算得到,动态展示当前导入进度
const SizedBox(height: 10),
LinearProgressIndicator(value: _progress),
const SizedBox(height: 12),
FilledButton(
onPressed: _running ? null : _start,
child: Text(_running ? '导入中...' : '开始导入'),
),
],
),
),
),
],
),
);
}
}
按钮与进度条布局说明:
- 多个SizedBox分隔元素,保证视觉间距均匀
- FilledButton的onPressed状态与
_running绑定,实现操作管控
8. 批量导入数据模型
复杂的批量导入场景需要完善的数据模型支撑,设计原则:
- 全量覆盖:包含任务全生命周期的核心信息,从创建到完成
- 可扩展:预留metadata字段,支持自定义扩展属性
- 易维护:通过枚举限定状态/类型,避免魔法值
class ImportTask {
final String id;
final String fileName;
final ImportType type;
final ImportStatus status;
final DateTime createdAt;
核心字段设计说明:
- id:唯一标识,采用时间戳+前缀的生成规则(后文实现)
- fileName:记录导入文件名,便于追溯
- type/status:枚举类型,限定取值范围,降低错误率
final DateTime? startedAt;
final DateTime? completedAt;
final double progress;
final int totalRecords;
final int processedRecords;
时间与进度字段说明:
- 时间字段采用可空类型(DateTime?),区分“未开始”和“已完成”状态
- progress保持0~1的浮点数格式,与前端进度条适配
- 记录数相关字段支持统计成功/失败数量,便于数据核对
final int successRecords;
final int failedRecords;
final List<ImportError> errors;
final Map<String, dynamic> metadata;
const ImportTask({
required this.id,
required this.fileName,
required this.type,
错误与扩展字段说明:
- errors:存储导入错误详情,支持后续展示错误列表
- metadata:键值对格式,可存储文件大小、导入人等扩展信息
- 构造方法使用required关键字,强制必填字段,避免空值错误
required this.status,
required this.createdAt,
this.startedAt,
this.completedAt,
this.progress = 0.0,
this.totalRecords = 0,
默认值设计:
- 非必填字段设置合理默认值,简化对象创建
- progress默认0.0,符合任务初始状态
- totalRecords默认0,避免空指针异常
this.processedRecords = 0,
this.successRecords = 0,
this.failedRecords = 0,
this.errors = const [],
this.metadata = const {},
});
}
集合字段默认值:
- errors和metadata默认空集合,避免调用时判空
- 构造方法使用const关键字,提升性能
class ImportError {
final int row;
final String field;
final String value;
final String message;
final ImportErrorType type;
const ImportError({
required this.row,
required this.field,
错误模型设计要点:
- row:记录错误行号(Excel行号从2开始),便于用户定位问题
- field:标记错误字段名,精准提示问题位置
- message:人性化错误提示,指导用户修正
required this.value,
required this.message,
required this.type,
});
}
enum ImportType {
manholes,
workOrders,
users,
}
枚举设计说明:
- ImportType限定导入数据类型,覆盖井盖、工单、用户三大核心场景
- 枚举值采用小写+下划线命名,符合Dart编码规范
enum ImportStatus {
pending,
running,
completed,
failed,
cancelled,
}
enum ImportErrorType {
validation,
duplicate,
format,
required,
}
状态与错误枚举说明:
- ImportStatus覆盖任务全生命周期状态,支持取消、失败等异常场景
- ImportErrorType分类错误类型,便于前端按类型展示不同提示
9. 批量导入状态管理
使用Provider实现跨组件状态管理,核心优势:
- 响应式:状态变更自动触发UI刷新
- 解耦:业务逻辑与UI渲染分离
- 可复用:导入状态可在多个页面共享
class BatchImportProvider extends ChangeNotifier {
List<ImportTask> _tasks = [];
ImportTask? _currentTask;
bool _loading = false;
String? _error;
状态存储设计:
- _tasks:存储所有导入任务,支持历史记录展示
- _currentTask:标记当前正在执行的任务,便于聚焦展示
- _loading/_error:全局加载状态和错误信息,统一管理
List<ImportTask> get tasks => _tasks;
ImportTask? get currentTask => _currentTask;
bool get loading => _loading;
String? get error => _error;
Getter方法设计:
- 对外暴露只读属性,避免外部直接修改状态
- 保持状态封装性,符合面向对象设计原则
Future<void> startImport({
required String fileName,
required ImportType type,
required List<Map<String, dynamic>> data,
}) async {
final task = ImportTask(
id: 'IMPORT_${DateTime.now().millisecondsSinceEpoch}',
fileName: fileName,
type: type,
任务创建逻辑:
- 任务ID采用“前缀+时间戳”,确保唯一性
- 接收fileName/type/data三个核心参数,满足导入基础需求
- async标记方法,支持异步处理
status: ImportStatus.pending,
createdAt: DateTime.now(),
totalRecords: data.length,
metadata: {
'fileSize': data.length,
'importedBy': 'current_user',
},
);
任务初始化:
- 初始状态设为pending,符合任务执行流程
- metadata记录文件大小、导入人等基础信息,便于追溯
- totalRecords从data.length获取,无需额外计算
_currentTask = task;
_tasks.insert(0, task);
notifyListeners();
await _processImport(task, data);
}
状态更新与处理:
- _tasks.insert(0, task)将新任务插入列表头部,符合用户查看习惯
- notifyListeners()触发UI刷新,展示最新任务状态
- 调用_processImport方法处理具体导入逻辑,职责分离
Future<void> _processImport(ImportTask task, List<Map<String, dynamic>> data) async {
try {
_currentTask = task.copyWith(
status: ImportStatus.running,
startedAt: DateTime.now(),
);
notifyListeners();
任务启动处理:
- 复制任务并更新状态为running,避免修改原对象
- 记录任务开始时间,完善生命周期数据
- 再次调用notifyListeners,确保状态同步
int successCount = 0;
int failedCount = 0;
List<ImportError> errors = [];
for (int i = 0; i < data.length; i++) {
final record = data[i];
final progress = (i + 1) / data.length;
循环处理初始化:
- 初始化计数变量,分别统计成功/失败数量
- 进度计算基于数据长度,确保进度精准
- 逐行处理数据,模拟真实导入场景
try {
await _processRecord(record, task.type);
successCount++;
} catch (e) {
failedCount++;
errors.add(ImportError(
row: i + 2,
field: 'unknown',
value: record.toString(),
异常处理逻辑:
- 捕获单条记录处理异常,不中断整体导入流程
- 错误行号设为i+2,适配Excel标题行(第一行)的场景
- 记录错误字段、值和信息,便于用户排查
message: e.toString(),
type: ImportErrorType.validation,
));
}
_currentTask = _currentTask!.copyWith(
progress: progress,
processedRecords: i + 1,
successRecords: successCount,
进度更新:
- 每次处理完记录后更新任务状态,实时反馈进度
- 复制任务对象更新,避免直接修改原对象
- 同步更新已处理、成功、失败数量
failedRecords: failedCount,
errors: errors,
);
notifyListeners();
await Future.delayed(const Duration(milliseconds: 100));
}
延时与刷新:
- 100ms延时模拟真实数据处理耗时
- 每次循环后调用notifyListeners,确保UI实时刷新
_currentTask = _currentTask!.copyWith(
status: failedCount == 0 ? ImportStatus.completed : ImportStatus.completed,
progress: 1.0,
completedAt: DateTime.now(),
);
notifyListeners();
任务完成处理:
- 无论是否有失败,最终状态均设为completed(可扩展为partially_completed)
- 进度强制设为1.0,确保进度条100%
- 记录任务完成时间,完善生命周期
} catch (e) {
_currentTask = _currentTask!.copyWith(
status: ImportStatus.failed,
completedAt: DateTime.now(),
);
_error = e.toString();
notifyListeners();
}
}
全局异常捕获:
- 捕获_processImport方法的全局异常,标记任务为failed
- 记录错误信息,便于前端展示
- 确保任务状态最终更新,避免卡死在running状态
Future<void> _processRecord(Map<String, dynamic> record, ImportType type) async {
await Future.delayed(const Duration(milliseconds: 50));
switch (type) {
case ImportType.manholes:
_validateManholeRecord(record);
break;
单条记录处理:
- 50ms延时模拟数据校验耗时
- 根据导入类型调用对应校验方法,职责分离
- switch语句覆盖所有ImportType类型,避免遗漏
case ImportType.workOrders:
_validateWorkOrderRecord(record);
break;
case ImportType.users:
_validateUserRecord(record);
break;
}
}
类型分支处理:
- 每个分支调用独立的校验方法,代码结构清晰
- 符合“单一职责”原则,便于后续修改某类数据的校验规则
void _validateManholeRecord(Map<String, dynamic> record) {
if (!record.containsKey('name') || record['name'].toString().trim().isEmpty) {
throw Exception('井盖名称不能为空');
}
if (!record.containsKey('latitude') || record['latitude'] == null) {
throw Exception('纬度不能为空');
}
井盖数据校验:
- 校验名称、纬度、经度三个核心字段
- 先检查字段是否存在,再检查值是否有效
- 抛出人性化异常信息,便于用户理解
if (!record.containsKey('longitude') || record['longitude'] == null) {
throw Exception('经度不能为空');
}
}
void _validateWorkOrderRecord(Map<String, dynamic> record) {
if (!record.containsKey('title') || record['title'].toString().trim().isEmpty) {
throw Exception('工单标题不能为空');
}
工单数据校验:
- 聚焦标题、片区两个核心字段
- 同样采用“存在性+有效性”双重校验
- 异常信息与字段对应,精准提示
if (!record.containsKey('district') || record['district'].toString().trim().isEmpty) {
throw Exception('片区不能为空');
}
}
void _validateUserRecord(Map<String, dynamic> record) {
if (!record.containsKey('name') || record['name'].toString().trim().isEmpty) {
throw Exception('用户名不能为空');
}
用户数据校验:
- 校验用户名、邮箱两个核心字段
- 保持与其他校验方法一致的风格,便于维护
- 异常信息统一格式,提升用户体验
if (!record.containsKey('email') || record['email'].toString().trim().isEmpty) {
throw Exception('邮箱不能为空');
}
}
Future<void> cancelImport() async {
if (_currentTask == null || _currentTask!.status != ImportStatus.running) {
return;
}
取消导入逻辑:
- 先判断当前任务状态,仅允许取消running状态的任务
- 提前return避免无效操作,提升性能
- 符合用户操作直觉,避免取消已完成/失败的任务
_currentTask = _currentTask!.copyWith(
status: ImportStatus.cancelled,
completedAt: DateTime.now(),
);
notifyListeners();
}
取消任务处理:
- 更新任务状态为cancelled,记录完成时间
- 调用notifyListeners刷新UI,反馈取消结果
- 保持与其他状态更新一致的逻辑
void clearError() {
_error = null;
notifyListeners();
}
void clearCompleted() {
_tasks.removeWhere((task) => task.status == ImportStatus.completed);
notifyListeners();
}
}
辅助方法设计:
- clearError:清空全局错误信息,用于错误提示关闭
- clearCompleted:移除已完成任务,清理历史记录
- 均调用notifyListeners,确保UI同步更新
10. 高级批量导入组件
高级组件在基础版本上扩展了更多实用功能,核心升级点:
- 类型选择:支持井盖、工单、用户三种数据类型
- 数据预览:导入前预览数据,降低错误率
- 历史记录:展示过往导入任务,便于追溯
- 模板下载:提供标准模板,规范导入数据格式
class AdvancedBatchImportWidget extends StatefulWidget {
const AdvancedBatchImportWidget({super.key});
State<AdvancedBatchImportWidget> createState() => _AdvancedBatchImportWidgetState();
}
组件基础结构:
- 采用StatefulWidget,管理组件内部状态
- 构造方法添加key参数,符合Flutter最佳实践
class _AdvancedBatchImportWidgetState extends State<AdvancedBatchImportWidget> {
final _fileController = TextEditingController();
ImportType _selectedType = ImportType.manholes;
List<Map<String, dynamic>> _previewData = [];
bool _showPreview = false;
内部状态管理:
- _fileController:控制文件选择输入框
- _selectedType:记录选中的导入类型,默认井盖数据
- _previewData:存储预览数据,支持导入前核对
- _showPreview:控制是否展示预览区域
void dispose() {
_fileController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Consumer<BatchImportProvider>(
builder: (context, provider, child) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
生命周期与构建方法:
- dispose方法释放TextEditingController,避免内存泄漏
- Consumer监听Provider状态变化,实现响应式更新
- SingleChildScrollView适配小屏设备,避免内容溢出
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context),
const SizedBox(height: 24),
_buildImportTypeSection(context),
const SizedBox(height: 24),
布局结构设计:
- Column作为根布局,按顺序排列各个功能区
- 每个功能区间隔24px,保证视觉呼吸感
- 调用独立方法构建各功能区,代码结构清晰
_buildFileSelectionSection(context),
const SizedBox(height: 24),
if (_showPreview) ...[
_buildPreviewSection(context),
const SizedBox(height: 24),
],
条件渲染预览区:
- 仅当_showPreview为true时展示预览区域
- 采用展开运算符(…),语法简洁
- 预览区同样间隔24px,保持布局一致性
_buildCurrentTaskSection(context, provider),
const SizedBox(height: 24),
_buildHistorySection(context, provider),
],
),
);
},
);
}
任务与历史记录区:
- 展示当前任务和历史记录,完善功能闭环
- 传递provider参数,获取导入状态
- 保持与其他区域一致的间距规范
Widget _buildHeader(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: Theme.of(context).primaryColor.withOpacity(0.1),
child: Icon(
Icons.file_upload,
color: Theme.of(context).primaryColor,
),
),
头部区域设计:
- CircleAvatar作为图标容器,视觉层次更丰富
- 采用主题色作为背景和图标色,保持风格统一
- Padding设为16px,与整体布局规范一致
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'批量导入',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
标题与描述设计:
- Expanded确保文本区域占满剩余空间
- 使用主题文本样式,保持与APP整体风格一致
- 标题加粗,提升视觉层级
const SizedBox(height: 4),
Text(
'支持 Excel、CSV 格式文件导入',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
),
],
),
),
],
),
],
),
),
);
}
描述文本设计:
- 灰色文本降低视觉权重,突出标题
- 字体大小14px,符合辅助文本规范
- 明确标注支持的文件格式,降低用户疑惑
Widget _buildImportTypeSection(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'导入类型',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
导入类型区标题:
- 标题使用titleMedium样式,加粗突出
- Card包裹,与其他功能区视觉统一
- Padding保持16px,布局规范一致
const SizedBox(height: 12),
Wrap(
spacing: 8,
children: ImportType.values.map((type) {
final isSelected = _selectedType == type;
return FilterChip(
label: Text(_getTypeDisplayName(type)),
selected: isSelected,
类型选择芯片设计:
- Wrap布局适配不同屏幕宽度,避免溢出
- FilterChip替代普通按钮,选中状态更直观
- spacing设为8px,芯片间距合理
onSelected: (selected) {
setState(() {
_selectedType = type;
_previewData.clear();
_showPreview = false;
});
},
芯片选中逻辑:
- 选中后清空预览数据,避免类型不匹配
- 隐藏预览区域,引导用户重新预览
- setState更新状态,触发UI刷新
backgroundColor: isSelected
? Theme.of(context).primaryColor.withOpacity(0.1)
: null,
selectedColor: Theme.of(context).primaryColor.withOpacity(0.2),
);
}).toList(),
),
芯片样式设计:
- 选中状态添加主题色背景,视觉反馈清晰
- 未选中状态使用默认背景,降低干扰
- 所有芯片样式统一,提升美观度
const SizedBox(height: 12),
Text(
_getTypeDescription(_selectedType),
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
),
],
),
),
);
}
类型描述文本:
- 12px小号字体,作为辅助说明
- 灰色文本,不抢焦点
- 根据选中类型动态展示描述,信息精准
String _getTypeDisplayName(ImportType type) {
switch (type) {
case ImportType.manholes:
return '井盖数据';
case ImportType.workOrders:
return '工单数据';
case ImportType.users:
return '用户数据';
}
}
类型名称转换:
- 将枚举值转换为中文名称,提升用户体验
- switch覆盖所有类型,避免遗漏
- 返回值简洁,直接用于UI展示
String _getTypeDescription(ImportType type) {
switch (type) {
case ImportType.manholes:
return '导入井盖位置、状态、维护记录等信息';
case ImportType.workOrders:
return '导入工单标题、状态、优先级、分配信息等';
case ImportType.users:
return '导入用户基本信息、权限、联系方式等';
}
}
类型描述转换:
- 详细说明每种类型可导入的内容,指导用户操作
- 描述语言通俗,无专业术语
- 与类型名称一一对应,信息准确
11. 小结
批量导入的实现展现了 Flutter 开发的几个重要原则:
数据模型:复杂的导入任务结构和错误管理
状态管理:使用 Provider 管理导入状态和进度
用户体验:实时进度、数据预览、错误处理
组件化设计:可复用的导入组件和验证逻辑
交互设计:文件选择、模板下载、任务管理
这样的设计不仅满足了批量导入的基本需求,还为后续的功能扩展(如多文件导入、断点续传、实时同步等)提供了良好的基础架构。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)