404 lines
12 KiB
Dart
404 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||
import '../theme/app_theme.dart';
|
||
import '../services/export_service.dart';
|
||
|
||
// Web 平台需要的导入
|
||
import 'dart:html' as html show Blob, Url, AnchorElement;
|
||
|
||
class ExportDataScreen extends StatefulWidget {
|
||
const ExportDataScreen({super.key});
|
||
|
||
@override
|
||
State<ExportDataScreen> createState() => _ExportDataScreenState();
|
||
}
|
||
|
||
class _ExportDataScreenState extends State<ExportDataScreen> {
|
||
final ExportService _exportService = ExportService();
|
||
DateTime _startDate = DateTime.now().subtract(const Duration(days: 7));
|
||
DateTime _endDate = DateTime.now();
|
||
bool _isExporting = false;
|
||
|
||
Future<void> _exportCSV() async {
|
||
setState(() {
|
||
_isExporting = true;
|
||
});
|
||
|
||
try {
|
||
final csvData = await _exportService.exportToCSV(
|
||
startDate: _startDate,
|
||
endDate: _endDate,
|
||
);
|
||
|
||
if (csvData.isEmpty || csvData.trim().isEmpty) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('所选日期范围内没有数据可导出'),
|
||
backgroundColor: AppTheme.warningColor,
|
||
),
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (kIsWeb) {
|
||
// Web 平台:下载文件
|
||
final blob = html.Blob([csvData], 'text/csv');
|
||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||
html.AnchorElement(href: url)
|
||
..setAttribute('download', 'autotime_export_${DateTime.now().millisecondsSinceEpoch}.csv')
|
||
..click();
|
||
html.Url.revokeObjectUrl(url);
|
||
} else {
|
||
// 移动端:复制到剪贴板
|
||
await Clipboard.setData(ClipboardData(text: csvData));
|
||
}
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(kIsWeb ? '文件已下载' : '数据已复制到剪贴板'),
|
||
backgroundColor: AppTheme.successColor,
|
||
),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(_getExportErrorMessage(e)),
|
||
backgroundColor: AppTheme.errorColor,
|
||
action: SnackBarAction(
|
||
label: '重试',
|
||
textColor: Colors.white,
|
||
onPressed: _exportReport,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() {
|
||
_isExporting = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _exportReport() async {
|
||
setState(() {
|
||
_isExporting = true;
|
||
});
|
||
|
||
try {
|
||
final report = await _exportService.exportStatsReport(
|
||
startDate: _startDate,
|
||
endDate: _endDate,
|
||
);
|
||
|
||
if (report.isEmpty || report.trim().isEmpty) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('所选日期范围内没有统计数据可导出'),
|
||
backgroundColor: AppTheme.warningColor,
|
||
),
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (kIsWeb) {
|
||
// Web 平台:下载文件
|
||
final blob = html.Blob([report], 'text/plain');
|
||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||
html.AnchorElement(href: url)
|
||
..setAttribute('download', 'autotime_report_${DateTime.now().millisecondsSinceEpoch}.txt')
|
||
..click();
|
||
html.Url.revokeObjectUrl(url);
|
||
} else {
|
||
// 移动端:复制到剪贴板
|
||
await Clipboard.setData(ClipboardData(text: report));
|
||
}
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(kIsWeb ? '文件已下载' : '报告已复制到剪贴板'),
|
||
backgroundColor: AppTheme.successColor,
|
||
),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(_getExportErrorMessage(e)),
|
||
backgroundColor: AppTheme.errorColor,
|
||
action: SnackBarAction(
|
||
label: '重试',
|
||
textColor: Colors.white,
|
||
onPressed: _exportReport,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() {
|
||
_isExporting = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _exportTodayReport() async {
|
||
setState(() {
|
||
_isExporting = true;
|
||
});
|
||
|
||
try {
|
||
final report = await _exportService.exportTodayReport();
|
||
|
||
if (report.isEmpty || report.trim().isEmpty) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(
|
||
content: Text('今日暂无数据可导出'),
|
||
backgroundColor: AppTheme.warningColor,
|
||
),
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (kIsWeb) {
|
||
// Web 平台:下载文件
|
||
final blob = html.Blob([report], 'text/plain');
|
||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||
html.AnchorElement(href: url)
|
||
..setAttribute('download', 'autotime_today_${DateTime.now().millisecondsSinceEpoch}.txt')
|
||
..click();
|
||
html.Url.revokeObjectUrl(url);
|
||
} else {
|
||
// 移动端:复制到剪贴板
|
||
await Clipboard.setData(ClipboardData(text: report));
|
||
}
|
||
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(kIsWeb ? '文件已下载' : '今日报告已复制到剪贴板'),
|
||
backgroundColor: AppTheme.successColor,
|
||
),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(_getExportErrorMessage(e)),
|
||
backgroundColor: AppTheme.errorColor,
|
||
action: SnackBarAction(
|
||
label: '重试',
|
||
textColor: Colors.white,
|
||
onPressed: _exportTodayReport,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
} finally {
|
||
if (mounted) {
|
||
setState(() {
|
||
_isExporting = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _selectDateRange() async {
|
||
final DateTimeRange? picked = await showDateRangePicker(
|
||
context: context,
|
||
firstDate: DateTime(2020),
|
||
lastDate: DateTime.now(),
|
||
initialDateRange: DateTimeRange(start: _startDate, end: _endDate),
|
||
);
|
||
|
||
if (picked != null) {
|
||
setState(() {
|
||
_startDate = picked.start;
|
||
_endDate = picked.end;
|
||
});
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final theme = Theme.of(context);
|
||
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('数据导出'),
|
||
),
|
||
body: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
// 日期范围选择
|
||
Card(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'选择日期范围',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: OutlinedButton.icon(
|
||
onPressed: _selectDateRange,
|
||
icon: const Icon(Icons.calendar_today),
|
||
label: Text(
|
||
'${_startDate.toString().split(' ')[0]} 至 ${_endDate.toString().split(' ')[0]}',
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// 导出选项
|
||
Card(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'导出选项',
|
||
style: theme.textTheme.titleMedium?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// CSV 导出
|
||
_buildExportOption(
|
||
theme,
|
||
icon: Icons.table_chart,
|
||
title: '导出 CSV 数据',
|
||
subtitle: '导出原始应用使用数据(CSV 格式)',
|
||
onTap: _exportCSV,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// 统计报告
|
||
_buildExportOption(
|
||
theme,
|
||
icon: Icons.description,
|
||
title: '导出统计报告',
|
||
subtitle: '导出时间范围内的统计报告(文本格式)',
|
||
onTap: _exportReport,
|
||
),
|
||
const SizedBox(height: 12),
|
||
|
||
// 今日报告
|
||
_buildExportOption(
|
||
theme,
|
||
icon: Icons.today,
|
||
title: '导出今日报告',
|
||
subtitle: '导出今日的详细统计报告',
|
||
onTap: _exportTodayReport,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
|
||
if (_isExporting)
|
||
const Padding(
|
||
padding: EdgeInsets.all(16),
|
||
child: Center(child: CircularProgressIndicator()),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildExportOption(
|
||
ThemeData theme, {
|
||
required IconData icon,
|
||
required String title,
|
||
required String subtitle,
|
||
required VoidCallback onTap,
|
||
}) {
|
||
return InkWell(
|
||
onTap: _isExporting ? null : onTap,
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: Container(
|
||
padding: const EdgeInsets.all(12),
|
||
decoration: BoxDecoration(
|
||
border: Border.all(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.1),
|
||
),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Icon(icon, color: AppTheme.primaryColor),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: theme.textTheme.bodyLarge?.copyWith(
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
subtitle,
|
||
style: theme.textTheme.bodySmall?.copyWith(
|
||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const Icon(Icons.chevron_right),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
String _getExportErrorMessage(Object error) {
|
||
final errorString = error.toString().toLowerCase();
|
||
if (errorString.contains('permission') || errorString.contains('权限')) {
|
||
return '需要授予应用使用权限';
|
||
} else if (errorString.contains('database') || errorString.contains('数据库')) {
|
||
return '数据库操作失败,请稍后重试';
|
||
} else if (errorString.contains('file') || errorString.contains('文件')) {
|
||
return '文件操作失败,请检查存储权限';
|
||
}
|
||
return '导出失败,请稍后重试';
|
||
}
|
||
}
|
||
|