chore(project): 初始化项目基础配置文件

- 添加 CodeGraph、Android 和通用 gitignore 配置
- 创建项目元数据文件跟踪 Flutter 项目属性
- 添加 Codex AI 指导文档 AGENTS.md 说明项目架构
- 配置代码分析选项 analysis_options.yaml
- 设置 Android 应用清单权限和 Kiosk 模式配置
- 实现中英文国际化支持 AppLocalizations
- 配置 GoRouter 应用路由导航
- 创建明亮工业控制风格的主题配置 AppTheme
This commit is contained in:
Developer
2026-06-04 11:19:44 +08:00
commit 5d28bf631b
85 changed files with 21423 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_theme.dart';
/// Toast 服务
/// 统一的消息提示管理
class ToastService {
/// 显示成功提示
static void showSuccess(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: AppTheme.successColor,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
),
);
}
/// 显示错误提示
static void showError(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: AppTheme.errorColor,
duration: const Duration(seconds: 4),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
),
);
}
/// 显示警告提示
static void showWarning(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.warning, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: AppTheme.warningColor,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
),
);
}
/// 显示信息提示
static void showInfo(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.info, color: Colors.white, size: 20),
const SizedBox(width: 12),
Expanded(child: Text(message)),
],
),
backgroundColor: AppTheme.primaryColor,
duration: const Duration(seconds: 3),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
),
);
}
}

View File

@@ -0,0 +1,24 @@
/// 常量定义
class Constants {
// 速度选项
static const List<String> speedOptions = ['低速', '中速', '高速'];
// 下针速度档位
static const List<int> needleSpeedLevels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 孔位列表
static const List<String> positions = [
'A1', 'A2', 'A3', 'A4', 'A5', 'A6',
'B1', 'B2', 'B3', 'B4', 'B5', 'B6',
'C1', 'C2', 'C3', 'C4', 'C5', 'C6',
'D1', 'D2', 'D3', 'D4', 'D5', 'D6',
];
// 默认步骤名称
static const List<String> defaultStepNames = ['混合', '吸磁', '吹气', '下针'];
// 时间单位
static const String timeUnitSeconds = '';
static const String timeUnitMinutes = '分钟';
static const String volumeUnit = 'μL';
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
/// 响应式布局工具类
/// 目标屏幕: 1920x1080
class ResponsiveLayout {
static const double targetWidth = 1920;
static const double targetHeight = 1080;
/// 获取屏幕宽度比例
static double widthPercent(BuildContext context, double percent) {
return MediaQuery.of(context).size.width * percent;
}
/// 获取屏幕高度比例
static double heightPercent(BuildContext context, double percent) {
return MediaQuery.of(context).size.height * percent;
}
/// 基于目标屏幕缩放宽度
static double scaleWidth(BuildContext context, double targetValue) {
final screenWidth = MediaQuery.of(context).size.width;
return targetValue * (screenWidth / targetWidth);
}
/// 基于目标屏幕缩放高度
static double scaleHeight(BuildContext context, double targetValue) {
final screenHeight = MediaQuery.of(context).size.height;
return targetValue * (screenHeight / targetHeight);
}
/// 基于目标屏幕缩放字体
static double scaleFont(BuildContext context, double targetFontSize) {
return scaleWidth(context, targetFontSize);
}
/// 预设布局尺寸
static double sidebarWidth(BuildContext context) => widthPercent(context, 0.25); // 480px on 1920
static double detailWidth(BuildContext context) => widthPercent(context, 0.21); // 400px on 1920
static double navWidth(BuildContext context) => widthPercent(context, 0.15); // 280px on 1920
static double cardWidth(BuildContext context) => widthPercent(context, 0.30); // ~576px
}
/// 响应式间距
class ResponsiveSpacing {
static double small(BuildContext context) => ResponsiveLayout.scaleWidth(context, 8);
static double medium(BuildContext context) => ResponsiveLayout.scaleWidth(context, 16);
static double large(BuildContext context) => ResponsiveLayout.scaleWidth(context, 24);
static double xlarge(BuildContext context) => ResponsiveLayout.scaleWidth(context, 32);
}

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_theme.dart';
/// 通用按钮组件 - 明亮工业风格
class CommonButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final bool enabled;
final Color? backgroundColor;
final Color? textColor;
final IconData? icon;
final bool isLoading;
final ButtonType type;
const CommonButton({
super.key,
required this.text,
this.onPressed,
this.enabled = true,
this.backgroundColor,
this.textColor,
this.icon,
this.isLoading = false,
this.type = ButtonType.primary,
});
@override
Widget build(BuildContext context) {
final bgColor = backgroundColor ?? _getDefaultBackgroundColor();
final fgColor = textColor ?? _getDefaultTextColor();
Widget content;
if (isLoading) {
content = Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: fgColor,
),
),
const SizedBox(width: 8),
Text(text, style: TextStyle(fontWeight: FontWeight.w500)),
],
);
} else if (icon != null) {
content = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18),
const SizedBox(width: 8),
Text(text, style: TextStyle(fontWeight: FontWeight.w500)),
],
);
} else {
content = Text(text, style: TextStyle(fontWeight: FontWeight.w500));
}
return ElevatedButton(
onPressed: enabled && !isLoading ? onPressed : null,
style: ElevatedButton.styleFrom(
backgroundColor: bgColor,
foregroundColor: fgColor,
disabledBackgroundColor: AppTheme.statusStopped.withValues(alpha: 0.3),
disabledForegroundColor: AppTheme.textTertiary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppTheme.radiusSm),
),
),
child: content,
);
}
Color _getDefaultBackgroundColor() {
switch (type) {
case ButtonType.primary:
return AppTheme.primaryColor;
case ButtonType.success:
return AppTheme.successColor;
case ButtonType.warning:
return AppTheme.warningColor;
case ButtonType.danger:
return AppTheme.errorColor;
case ButtonType.secondary:
return AppTheme.bgSurface;
}
}
Color _getDefaultTextColor() {
switch (type) {
case ButtonType.secondary:
return AppTheme.textPrimary;
default:
return AppTheme.textOnPrimary;
}
}
}
enum ButtonType { primary, success, warning, danger, secondary }

View File

@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_theme.dart';
/// 通用卡片组件 - 明亮工业风格
class CommonCard extends StatelessWidget {
final Widget child;
final VoidCallback? onTap;
final bool selected;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
const CommonCard({
super.key,
required this.child,
this.onTap,
this.selected = false,
this.padding,
this.margin,
});
@override
Widget build(BuildContext context) {
return Container(
margin: margin ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Material(
color: selected ? AppTheme.bgCardHover : AppTheme.bgCard,
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
elevation: 0,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
child: Container(
padding: padding ?? const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppTheme.radiusMd),
border: selected
? Border.all(color: AppTheme.primaryColor, width: 2)
: Border.all(color: AppTheme.borderLight, width: 1),
boxShadow: AppTheme.shadowCard,
),
child: child,
),
),
),
);
}
}

View File

@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
/// 确认对话框组件
class CommonDialog {
/// 显示确认对话框
static Future<bool?> showConfirm({
required BuildContext context,
required String title,
required String content,
String confirmText = '确认',
String cancelText = '取消',
bool isDestructive = false,
}) {
return showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text(cancelText),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: isDestructive ? Colors.red : null,
),
child: Text(confirmText),
),
],
),
);
}
/// 显示信息对话框
static Future<void> showInfo({
required BuildContext context,
required String title,
required String content,
String confirmText = '确认',
}) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text(confirmText),
),
],
),
);
}
/// 显示输入对话框
static Future<String?> showInput({
required BuildContext context,
required String title,
String? hintText,
String? initialValue,
String confirmText = '确认',
String cancelText = '取消',
}) {
final controller = TextEditingController(text: initialValue);
return showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: TextField(
decoration: InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(),
),
controller: controller,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(cancelText),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, controller.text),
child: Text(confirmText),
),
],
),
);
}
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import '../../../core/theme/app_theme.dart';
/// 空状态组件
/// 统一的空数据展示样式
class EmptyStateWidget extends StatelessWidget {
final IconData icon;
final String message;
final String? actionText;
final VoidCallback? onAction;
const EmptyStateWidget({
super.key,
required this.icon,
required this.message,
this.actionText,
this.onAction,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 64,
color: AppTheme.idleColor,
),
const SizedBox(height: 16),
Text(
message,
style: TextStyle(
color: AppTheme.textSecondary,
fontSize: 16,
),
),
if (actionText != null && onAction != null) ...[
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: onAction,
icon: const Icon(Icons.add, size: 20),
label: Text(actionText!),
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor,
foregroundColor: Colors.white,
),
),
],
],
),
);
}
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_theme.dart';
/// 状态指示器组件 - 明亮工业风格
class StatusIndicator extends StatelessWidget {
final String text;
final DeviceStatusType status;
final double size;
const StatusIndicator({
super.key,
required this.text,
required this.status,
this.size = 10,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _getStatusColor(),
),
),
const SizedBox(width: 6),
Text(
text,
style: TextStyle(
color: _getStatusColor(),
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
);
}
Color _getStatusColor() {
switch (status) {
case DeviceStatusType.running:
return AppTheme.statusRunning;
case DeviceStatusType.idle:
return AppTheme.statusStopped;
case DeviceStatusType.paused:
return AppTheme.statusPaused;
case DeviceStatusType.error:
return AppTheme.statusError;
case DeviceStatusType.success:
return AppTheme.statusRunning;
}
}
}
enum DeviceStatusType { running, idle, paused, error, success }