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 createState() => _ExportDataScreenState(); } class _ExportDataScreenState extends State { final ExportService _exportService = ExportService(); DateTime _startDate = DateTime.now().subtract(const Duration(days: 7)); DateTime _endDate = DateTime.now(); bool _isExporting = false; Future _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 _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 _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 _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 '导出失败,请稍后重试'; } }