Files
kuaishai2/lib/features/programs/pages/programs_page.dart
Developer 5d28bf631b chore(project): 初始化项目基础配置文件
- 添加 CodeGraph、Android 和通用 gitignore 配置
- 创建项目元数据文件跟踪 Flutter 项目属性
- 添加 Codex AI 指导文档 AGENTS.md 说明项目架构
- 配置代码分析选项 analysis_options.yaml
- 设置 Android 应用清单权限和 Kiosk 模式配置
- 实现中英文国际化支持 AppLocalizations
- 配置 GoRouter 应用路由导航
- 创建明亮工业控制风格的主题配置 AppTheme
2026-06-04 11:19:44 +08:00

509 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:file_picker/file_picker.dart';
import 'dart:io';
import '../../../core/localization/app_localizations.dart';
import '../../../core/theme/app_theme.dart';
import '../../../shared/widgets/common_button.dart';
import '../models/program.dart';
import '../providers/programs_provider.dart';
import '../widgets/program_form_dialog.dart';
import '../services/program_import_service.dart';
/// 程序管理页面
class ProgramsPage extends ConsumerStatefulWidget {
const ProgramsPage({super.key});
@override
ConsumerState<ProgramsPage> createState() => _ProgramsPageState();
}
class _ProgramsPageState extends ConsumerState<ProgramsPage> {
final Set<int> _selectedIds = {};
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final programsState = ref.watch(programsProvider);
return Scaffold(
body: Container(
color: AppTheme.backgroundColor,
child: Column(
children: [
// 顶部导航栏
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
// 返回按钮
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.go('/'),
),
const SizedBox(width: 16),
Text(
l10n?.programs ?? '程序管理',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const Spacer(),
// 新增按钮
CommonButton(
text: l10n?.addProgram ?? '新增',
icon: Icons.add,
type: ButtonType.primary,
onPressed: () => _showAddDialog(context, ref),
),
const SizedBox(width: 12),
// 导入按钮
CommonButton(
text: l10n?.importProgram ?? '导入',
icon: Icons.file_upload,
type: ButtonType.secondary,
onPressed: () => _importPrograms(context, ref),
),
],
),
),
// 程序列表表格
Expanded(
child: programsState.isLoading
? const Center(child: CircularProgressIndicator())
: programsState.programs.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.folder_open,
size: 64,
color: AppTheme.idleColor,
),
const SizedBox(height: 16),
Text(
l10n?.noData ?? '暂无数据',
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 16,
),
),
],
),
)
: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(2, 2),
),
],
),
child: Column(
children: [
// 表头
_buildTableHeader(l10n, programsState.programs),
// 表格内容
Expanded(
child: ListView.builder(
itemCount: programsState.programs.length,
itemBuilder: (context, index) {
final program = programsState.programs[index];
final isSelected = _selectedIds.contains(program.id);
return _buildTableRow(
context,
ref,
l10n,
program,
isSelected,
index == programsState.programs.length - 1,
);
},
),
),
],
),
),
),
// 底部操作栏
if (programsState.programs.isNotEmpty)
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
Text(
'${l10n?.selected ?? '已选择'}: ${_selectedIds.length}',
style: TextStyle(color: AppTheme.textSecondary),
),
const Spacer(),
if (_selectedIds.isNotEmpty)
CommonButton(
text: l10n?.deleteProgram ?? '删除',
icon: Icons.delete,
type: ButtonType.danger,
onPressed: () => _showDeleteConfirmDialog(
context,
ref,
l10n,
_selectedIds.toList(),
),
),
],
),
),
],
),
),
);
}
/// 表头
Widget _buildTableHeader(AppLocalizations? l10n, List<Program> programs) {
final allSelected = _selectedIds.length == programs.length && programs.isNotEmpty;
return Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
),
child: Row(
children: [
// 复选框
SizedBox(
width: 50,
child: Checkbox(
value: allSelected,
onChanged: (value) {
setState(() {
if (value == true) {
_selectedIds.clear();
_selectedIds.addAll(programs.map((p) => p.id!));
} else {
_selectedIds.clear();
}
});
},
),
),
// 编号
SizedBox(
width: 100,
child: Text(
l10n?.programCode ?? '编号',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
),
// 名称
Expanded(
flex: 2,
child: Text(
l10n?.programName ?? '名称',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
),
// 创建时间
Expanded(
child: Text(
l10n?.createTime ?? '创建时间',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
),
// 状态
SizedBox(
width: 80,
child: Text(
'状态',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
),
),
// 操作
SizedBox(
width: 150,
child: Text(
l10n?.detail ?? '操作',
style: TextStyle(
fontWeight: FontWeight.w600,
color: AppTheme.textPrimary,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
/// 表格行
Widget _buildTableRow(
BuildContext context,
WidgetRef ref,
AppLocalizations? l10n,
Program program,
bool isSelected,
bool isLast,
) {
return Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: isSelected ? AppTheme.primaryLight.withValues(alpha: 0.2) : null,
border: isLast
? null
: Border(
bottom: BorderSide(
color: AppTheme.idleColor.withValues(alpha: 0.2),
),
),
),
child: Row(
children: [
// 复选框
SizedBox(
width: 50,
child: Checkbox(
value: isSelected,
onChanged: (value) {
setState(() {
if (value == true) {
_selectedIds.add(program.id!);
} else {
_selectedIds.remove(program.id!);
}
});
},
),
),
// 编号
SizedBox(
width: 100,
child: Text(
program.code,
style: TextStyle(color: AppTheme.textPrimary),
),
),
// 名称
Expanded(
flex: 2,
child: Text(
program.name,
style: TextStyle(color: AppTheme.textPrimary),
),
),
// 创建时间
Expanded(
child: Text(
program.createdAt,
style: TextStyle(color: AppTheme.textSecondary),
),
),
// 状态
SizedBox(
width: 80,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: program.status == 1
? AppTheme.successColor.withValues(alpha: 0.1)
: AppTheme.idleColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
program.status == 1 ? '启用' : '停用',
style: TextStyle(
color: program.status == 1
? AppTheme.successColor
: AppTheme.idleColor,
fontSize: 12,
),
textAlign: TextAlign.center,
),
),
),
// 操作按钮
SizedBox(
width: 150,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.edit, size: 20),
color: AppTheme.primaryColor,
onPressed: () => _showEditDialog(context, ref, program),
),
IconButton(
icon: const Icon(Icons.delete, size: 20),
color: AppTheme.errorColor,
onPressed: () => _showDeleteConfirmDialog(
context,
ref,
l10n,
[program.id!],
),
),
IconButton(
icon: const Icon(Icons.visibility, size: 20),
color: AppTheme.textSecondary,
onPressed: () => context.go('/programs/${program.id}'),
),
],
),
),
],
),
);
}
/// 显示新增对话框
void _showAddDialog(BuildContext context, WidgetRef ref) {
showDialog(
context: context,
builder: (context) => const ProgramFormDialog(),
);
}
/// 导入程序
Future<void> _importPrograms(BuildContext context, WidgetRef ref) async {
try {
// 选择文件
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
allowMultiple: false,
);
if (result == null || result.files.isEmpty) {
return;
}
final file = result.files.first;
if (file.path == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('无法读取文件'),
backgroundColor: AppTheme.errorColor,
),
);
return;
}
// 读取文件内容
final jsonContent = await File(file.path!).readAsString();
// 导入程序
final importedCount = await ProgramImportService.instance.importFromJson(jsonContent);
// 刷新程序列表
ref.read(programsProvider.notifier).loadPrograms();
// 显示结果
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('成功导入 $importedCount 个程序'),
backgroundColor: importedCount > 0 ? AppTheme.successColor : AppTheme.warningColor,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('导入失败: ${e.toString()}'),
backgroundColor: AppTheme.errorColor,
),
);
}
}
/// 显示编辑对话框
void _showEditDialog(BuildContext context, WidgetRef ref, Program program) {
showDialog(
context: context,
builder: (context) => ProgramFormDialog(program: program),
);
}
/// 显示删除确认对话框
void _showDeleteConfirmDialog(
BuildContext context,
WidgetRef ref,
AppLocalizations? l10n,
List<int> ids,
) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n?.confirm ?? '确认'),
content: Text(
ids.length == 1
? '确定要删除此程序吗?'
: '确定要删除选中的 ${ids.length} 个程序吗?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(l10n?.cancel ?? '取消'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.errorColor,
foregroundColor: Colors.white,
),
onPressed: () async {
final notifier = ref.read(programsProvider.notifier);
await notifier.deletePrograms(ids);
setState(() {
_selectedIds.removeAll(ids);
});
Navigator.of(context).pop();
},
child: Text(l10n?.confirm ?? '确认'),
),
],
),
);
}
}