Instruction file imported from clutchtechnology/ceramic-waterpump-flutter (
.cursor/rules/app.mdc). Copyright stays with the author.
水泵房监控系统 Flutter 前端开发规则
项目标识
- 项目名称: ceramic-waterpump-flutter
- 技术栈: Flutter 3.29+ + Dart 3.4+ + WebSocket
- 目标平台: Windows (主要) / Android / iOS / Web
- 核心理念: WebSocket 实时通信 + 工业风格 UI + 固定分辨率设计
架构原则
1. WebSocket 优先策略
- 实时通信: 使用 WebSocket 替代 HTTP 轮询,实现 0.1s 级别的数据推送
- 自动重连: 指数退避重连策略 (1s → 2s → 4s → 8s → 16s → 30s)
- 心跳保活: 客户端每 15s 发送心跳,防止连接超时
- 消息订阅: 支持
realtime(实时数据) 和device_status(设备状态) 两个频道
2. 固定分辨率设计
- 目标设备: 10.4 英寸工业触摸屏
- 分辨率: 1280×800 (固定,不可调整)
- 窗口模式: 无边框窗口,隐藏原生标题栏
- 布局策略: 基于固定尺寸设计,不需要响应式布局
3. 工业风格 UI
- 主题色: 科技蓝 (#00D4FF)
- 背景色: 深色背景 (#0A0E27, #1A1F3A)
- 字体: 等宽字体,清晰易读
- 动画: 简洁流畅,避免过度动画
4. 数据流架构
Backend WebSocket (0.1s) → WebSocketService (单例) → Callbacks → UI Update
↓
State Management
核心组件
WebSocket 层
文件: lib/services/websocket_service.dart
- 单例模式: 全局唯一的 WebSocket 连接管理器
- 连接状态:
disconnected,connecting,connected,reconnecting - 消息类型:
realtime_data: 实时数据推送 (6台水泵 + 1个压力表)device_status: 设备通信状态 (DB1/DB3)heartbeat: 心跳消息error: 错误消息
- 回调机制:
onRealtimeDataUpdate: 实时数据更新回调onDeviceStatusUpdate: 设备状态更新回调onStateChanged: 连接状态变化回调onError: 错误回调
服务层
文件: lib/services/
- realtime_service.dart: 实时数据服务 (封装 WebSocket 实时数据)
- sensor_status_service.dart: 设备状态服务
- health_service.dart: 健康检查服务
- alarm_service.dart: 报警服务
- history_service.dart: 历史数据查询服务 (HTTP API)
数据模型层
文件: lib/models/
- pump_data.dart: 水泵数据模型
RealtimeBatchResponse: 批量实时数据响应PumpData: 单个水泵数据PressureData: 压力表数据
- sensor_status_model.dart: 设备状态模型
DeviceStatusResponse: 设备状态响应DeviceStatusItem: 单个设备状态
UI 层
文件: lib/pages/, lib/widgets/
- main_page.dart: 主页面 (分屏布局)
- sensor_status_page.dart: 设备状态页面
- history_data_page.dart: 历史数据页面
- alarm_log_page.dart: 报警日志页面
- settings_page.dart: 设置页面 (密码保护)
自定义组件:
- custom_card_widget.dart: 科技风格卡片
- tech_line_widgets.dart: 科技风格线条和装饰
- health_indicator.dart: 健康状态指示器
- threshold_settings_widget.dart: 阈值设置组件
编码规范
1. 命名规范
1.1 基础命名规则
- 文件名: 小写下划线
snake_case.dart - 类名: 大驼峰
PascalCase - 函数/变量: 小驼峰
camelCase - 常量: 小驼峰
camelCase - 私有成员: 下划线前缀
_private
2. 注释规范
使用清晰的注释风格:
// 1. 初始化 WebSocket 服务
WebSocketService() {
_socket = null;
_state = WebSocketState.disconnected;
}
// 2. 连接到服务器
Future<void> connect() async {
if (_state == WebSocketState.connected) return;
// 连接逻辑
}
文件头部注释:
// WebSocket 连接服务 - 水泵房监控系统
// 功能: 单例 WebSocket 连接管理,支持自动重连、心跳检测、消息分发
2.1 禁止使用 Emoji 表情符号
原则: 注释中不使用任何 emoji 图标或表情符号,保持代码的专业性和可读性。
正确的注释:
// 1. 初始化 WebSocket 服务
// 注意:这里需要检查连接状态
// 警告:不要在主线程执行耗时操作
// 成功:连接建立完成
// 错误:连接失败
错误的注释(禁止使用):
// 初始化 WebSocket 服务
// 注意:这里需要检查连接状态
// 错误:连接失败
// 成功:连接建立完成
// 重要:性能优化
// 提示:可以使用缓存
原因说明:
- 编码兼容性: Emoji 可能在某些编辑器或终端中显示异常
- 代码审查: 纯文本注释更易于代码审查和搜索
- 专业性: 工业控制系统代码应保持严谨的专业风格
- 版本控制: Emoji 在 Git diff 中可能显示为乱码
- 跨平台: 不同操作系统对 Emoji 的支持程度不同
3. 代码设计原则
避免过度抽象:
- 不要提前抽象:需要用的时候再抽象,不要一开始就创建大量工具方法
- 避免冗余方法:一个文件不要抽象出太多方法,保持简洁
- 实用主义:能直接写就直接写,不要为了"优雅"而过度封装
好的做法:
// 1. 更新显示数据
void updateDisplay(RealtimeBatchResponse data) {
if (mounted) {
setState(() {
voltageLabel.text = '${data.data.pumps[0].voltage.toStringAsFixed(1)} V';
currentLabel.text = '${data.data.pumps[0].current.toStringAsFixed(1)} A';
});
}
}
过度抽象:
String _formatVoltage(double voltage) {
return '${voltage.toStringAsFixed(1)} V';
}
String _formatCurrent(double current) {
return '${current.toStringAsFixed(1)} A';
}
void _updateLabel(Widget label, String text) {
label.text = text;
}
void updateDisplay(RealtimeBatchResponse data) {
if (mounted) {
setState(() {
final voltageText = _formatVoltage(data.data.pumps[0].voltage);
final currentText = _formatCurrent(data.data.pumps[0].current);
_updateLabel(voltageLabel, voltageText);
_updateLabel(currentLabel, currentText);
});
}
}
4. WebSocket 使用规范
// 正确:在 initState 中初始化 WebSocket
@override
void initState() {
super.initState();
_wsService = WebSocketService();
_wsService.onRealtimeDataUpdate = _handleRealtimeData;
_wsService.onStateChanged = _handleStateChanged;
_wsService.connect();
}
// 正确:在 dispose 中释放资源
@override
void dispose() {
_wsService.dispose();
super.dispose();
}
// 正确:处理连接状态变化
void _handleStateChanged(WebSocketState state) {
if (mounted) {
setState(() {
_connectionState = state;
});
}
}
// 错误:不检查 mounted 状态
void _handleRealtimeData(RealtimeBatchResponse data) {
setState(() { // 可能在 dispose 后调用
_data = data;
});
}
2. 状态管理规范
// 正确:使用 StatefulWidget + setState
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
RealtimeBatchResponse? _data;
void _updateData(RealtimeBatchResponse data) {
if (mounted) {
setState(() {
_data = data;
});
}
}
}
// 正确:使用 Provider (可选)
class ThresholdConfigProvider extends ChangeNotifier {
Map<String, dynamic> _config = {};
void updateConfig(Map<String, dynamic> config) {
_config = config;
notifyListeners();
}
}
// 错误:直接修改状态不调用 setState
_data = newData; // UI 不会更新
3. 异步操作规范
// 正确:使用 async/await
Future<void> _loadData() async {
try {
final response = await HistoryService.queryHistory(
pumpId: 1,
parameter: 'voltage',
interval: '5m',
);
if (mounted) {
setState(() {
_historyData = response;
});
}
} catch (e) {
_showError('加载失败: $e');
}
}
// 正确:使用 FutureBuilder
FutureBuilder<HealthResponse>(
future: HealthService.checkHealth(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('健康');
}
return CircularProgressIndicator();
},
)
// 错误:不处理异常
final data = await api.getData(); // 可能抛出异常
4. UI 布局规范
// 正确:使用固定尺寸布局
Container(
width: 1280,
height: 800,
child: Row(
children: [
SizedBox(width: 640, child: LeftPanel()),
SizedBox(width: 640, child: RightPanel()),
],
),
)
// 正确:使用 Expanded 分配剩余空间
Row(
children: [
SizedBox(width: 200, child: Sidebar()),
Expanded(child: MainContent()),
],
)
// 错误:使用响应式布局
MediaQuery.of(context).size.width // 不需要,固定 1280
5. 颜色和样式规范
// 正确:使用 TechColors 常量
import '../utils/constants.dart';
Container(
decoration: BoxDecoration(
color: TechColors.cardBackground,
border: Border.all(color: TechColors.primary),
),
)
// 正确:使用自定义组件
CustomCardWidget(
title: '1#水泵',
child: PumpDataDisplay(),
)
// 错误:硬编码颜色
Container(
color: Color(0xFF00D4FF), // 应使用 TechColors.primary
)
6. 错误处理规范
// 正确:显示用户友好的错误信息
void _showError(String message) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
duration: Duration(seconds: 3),
),
);
}
}
// 正确:WebSocket 错误处理
_wsService.onError = (error) {
_showError('连接错误: $error');
};
// 错误:忽略错误
try {
await api.getData();
} catch (e) {
// 什么都不做
}
API 接口规范
WebSocket 接口 (主要)
端点: ws://localhost:8081/ws/realtime
客户端消息:
// 订阅实时数据
wsService.subscribeRealtime();
// 订阅设备状态
wsService.subscribeDeviceStatus();
// 发送心跳
wsService.send({
'type': 'heartbeat',
'timestamp': DateTime.now().toIso8601String(),
});
服务端推送:
// 实时数据推送 (0.1s 间隔)
{
"type": "realtime_data",
"success": true,
"timestamp": "2026-02-07T10:30:00.000Z",
"source": "plc",
"data": {
"pumps": [
{"id": 1, "voltage": 380.5, "current": 12.3, "power": 5.6, ...}
],
"pressure": {"value": 0.45, "status": "normal"}
}
}
// 设备状态推送 (0.1s 间隔)
{
"type": "device_status",
"success": true,
"timestamp": "2026-02-07T10:30:00.000Z",
"source": "plc",
"data": {
"db1": [...],
"db3": [...]
},
"summary": {"total": 6, "normal": 6, "error": 0}
}
HTTP 接口 (降级)
Base URL: http://localhost:8081/api
// 历史数据查询
final response = await HistoryService.queryHistory(
pumpId: 1,
parameter: 'voltage',
interval: '5m',
start: DateTime.now().subtract(Duration(hours: 1)),
end: DateTime.now(),
);
// 健康检查
final health = await HealthService.checkHealth();
// 阈值配置
final config = await ThresholdService.getConfig();
await ThresholdService.updateConfig(newConfig);
性能优化
1. WebSocket 连接管理
// 正确:单例模式,全局共享连接
final wsService = WebSocketService();
// 正确:页面切换时不断开连接
@override
void dispose() {
// 不调用 wsService.disconnect()
super.dispose();
}
// 错误:每个页面创建新连接
final wsService = WebSocketService()..connect(); // 会创建多个连接
2. UI 更新优化
// 正确:使用 ValueNotifier 减少重建
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final ValueNotifier<double> _voltage = ValueNotifier(0.0);
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<double>(
valueListenable: _voltage,
builder: (context, value, child) {
return Text('$value V');
},
);
}
}
// 错误:频繁调用 setState 重建整个页面
void _updateData(data) {
setState(() { // 重建整个页面
_allData = data;
});
}
3. 图表性能优化
// 正确:限制数据点数量
List<FlSpot> _prepareChartData(List<HistoryPoint> data) {
if (data.length > 100) {
// 采样:每 N 个点取 1 个
final step = data.length ~/ 100;
return data
.where((point) => data.indexOf(point) % step == 0)
.map((point) => FlSpot(point.x, point.y))
.toList();
}
return data.map((point) => FlSpot(point.x, point.y)).toList();
}
// 错误:显示所有数据点
final spots = data.map((p) => FlSpot(p.x, p.y)).toList(); // 可能有 10000+ 个点
常见问题
1. WebSocket 连接失败
原因: 后端服务未启动、URL 错误、网络问题
解决:
// 检查后端服务状态
final health = await HealthService.checkHealth();
// 检查 WebSocket URL
print(WebSocketService().wsUrl); // ws://localhost:8081/ws/realtime
// 查看连接状态
wsService.onStateChanged = (state) {
print('WebSocket 状态: $state');
};
2. 数据不更新
原因: 未订阅频道、回调未设置、mounted 检查失败
解决:
// 确保订阅了频道
wsService.subscribeRealtime();
// 确保设置了回调
wsService.onRealtimeDataUpdate = (data) {
if (mounted) {
setState(() {
_data = data;
});
}
};
// 检查连接状态
if (wsService.state != WebSocketState.connected) {
wsService.connect();
}
3. 内存泄漏
原因: 未释放 WebSocket 回调、定时器未取消
解决:
@override
void dispose() {
// 清理回调
wsService.onRealtimeDataUpdate = null;
wsService.onDeviceStatusUpdate = null;
// 取消定时器
_timer?.cancel();
super.dispose();
}
4. UI 卡顿
原因: 频繁 setState、大量数据渲染、复杂布局
解决:
// 使用 ValueNotifier 代替 setState
final _voltage = ValueNotifier<double>(0.0);
// 限制更新频率
Timer? _updateTimer;
void _scheduleUpdate(data) {
_updateTimer?.cancel();
_updateTimer = Timer(Duration(milliseconds: 100), () {
setState(() => _data = data);
});
}
// 使用 const 构造函数
const Text('标题', style: TextStyle(fontSize: 16));
文件结构速查
lib/
├── main.dart # 入口文件
├── api/
│ ├── api.dart # API 配置 (URL)
│ ├── api_client.dart # HTTP 客户端封装
│ └── index.dart # 导出
├── models/
│ ├── pump_data.dart # ★ 水泵数据模型
│ └── sensor_status_model.dart # ★ 设备状态模型
├── services/
│ ├── websocket_service.dart # ★ WebSocket 服务 (核心)
│ ├── realtime_service.dart # 实时数据服务
│ ├── sensor_status_service.dart # 设备状态服务
│ ├── health_service.dart # 健康检查服务
│ ├── alarm_service.dart # 报警服务
│ └── history_service.dart # 历史数据服务
├── pages/
│ ├── main_page.dart # ★ 主页面
│ ├── sensor_status_page.dart # 设备状态页面
│ ├── history_data_page.dart # 历史数据页面
│ ├── alarm_log_page.dart # 报警日志页面
│ └── settings_page.dart # 设置页面
├── widgets/
│ ├── custom_card_widget.dart # 自定义卡片
│ ├── tech_line_widgets.dart # 科技风格组件
│ ├── health_indicator.dart # 健康指示器
│ ├── threshold_settings_widget.dart # 阈值设置
│ ├── data_display/ # 数据显示组件
│ └── icons/ # 自定义图标
├── providers/
│ └── threshold_config_provider.dart # 阈值配置 Provider
└── utils/
└── constants.dart # ★ 常量定义
开发流程
1. 启动后端服务
# 在后端项目目录
cd ceramic-waterpump-backend
start_mock.bat # Mock 模式
# 或
start_production.bat # 生产模式
2. 启动 Flutter 应用
# Windows 平台
flutter run -d windows
# 热重载
r # 热重载
R # 热重启
q # 退出
3. 调试 WebSocket
// 在代码中添加日志
wsService.onStateChanged = (state) {
print('[WS] 状态变化: $state');
};
wsService.onRealtimeDataUpdate = (data) {
print('[WS] 收到实时数据: ${data.data.pumps.length} 个水泵');
};
wsService.onError = (error) {
print('[WS] 错误: $error');
};
4. 构建发布版本
# Windows
flutter build windows --release
# 输出目录
build/windows/x64/runner/Release/
技术约定
依赖管理
dependencies:
flutter: sdk
http: ^1.2.0 # HTTP 客户端
dio: ^5.4.0 # 高级 HTTP 客户端
fl_chart: ^0.68.0 # 图表库
window_manager: ^0.3.9 # 窗口管理
shared_preferences: ^2.2.3 # 本地存储
intl: ^0.20.2 # 国际化
代码风格
- 使用
flutter analyze检查代码 - 遵循 Dart 官方代码风格
- 类名使用大驼峰
PascalCase - 变量/函数使用小驼峰
camelCase - 私有成员使用下划线前缀
_private - 常量使用小驼峰
camelCase
AI 编码指令
- WebSocket 优先: 实时数据必须使用 WebSocket,HTTP 仅用于历史查询
- 单例模式: WebSocketService 必须使用单例,避免多个连接
- 状态检查: 所有 setState 前必须检查
mounted - 资源释放: dispose 中必须清理回调和定时器
- 错误处理: 所有异步操作必须有 try-catch
- 固定布局: 使用固定尺寸 1280×800,不需要响应式
- 工业风格: 使用 TechColors 常量,保持科技感
- 性能优化: 使用 ValueNotifier、限制数据点、const 构造函数
- 协议兼容: 严格遵循后端 WebSocket 协议规范
- 用户体验: 显示连接状态、加载指示器、错误提示
参考文档
docs/WEBSOCKET_PROTOCOL.md- WebSocket 协议规范(与后端共享)README.md- 项目说明CODE_REVIEW.md- 代码审查清单- 后端文档:
ceramic-waterpump-backend/.cursor/rules/waterpump.mdc
- 协议规范: 严格遵循
docs/WEBSOCKET_PROTOCOL.md中定义的消息格式和通信流程。
其他规范
- PowerShell 命令:不支持
&&,使用分号;分隔命令 - 称呼:每次回答必须称呼我为"大王"
- 测试文件:不要创建多余的 md/py/test 文件,测试完毕后一定要删除,并且我的任何测试代码不要使用 emoji.
- 文档管理:md 文件需要放到
vdoc/目录里面 - 代码整洁:目录务必整洁,修改代码时删除旧代码,不要冗余
- 回答执行规范:你是一个很严格的python pyqt6写上位机的高手,你很严谨认真,且对代码很严苛,不会写无用冗余代码,并且很多问题,对于我希望实现的效果和架构你会认真思考,如果我的提议不好或者你有更好的方案,你会规劝我.
- 反驳我的回答 对于我说的需求等的话,肯定会有一些东西说的不专业,如果你理解了的话,就回答我,"大王,小的罪该万死,但是这个XXXX"这样回答.
- 编码问题 我的代码文件肯定会就是有中文和python代码,以及可能会有图标,所以的话,生成的代码需要规避编码问题错误.
- log以及代码文件 我的代码文件以及log的输出的话,等一切不要使用图标等标注. .这样的.
- 不要虚构 回答我以及生成的md文件之中一定要和我的实际的代码文件相关,而不是虚构的.
- 不使用虚拟环境启动python
- 必须真实有效的回答我,不能虚构不要虚构任何我项目没有的文件,回答也必须严谨有效,而不是虚构.
- 测试脚本和启动脚本文件最小化原则尽量不要创建脚本而是直接给我一组命令行就行,如果需要保留为脚本我会提创建脚本的需求.