first commit

This commit is contained in:
ytc1012
2025-11-13 15:45:28 +08:00
commit 6b321890c0
54 changed files with 8412 additions and 0 deletions

View File

@@ -0,0 +1,403 @@
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 '导出失败,请稍后重试';
}
}