Flutter 标准分层目录(对齐 SpringBoot 思想)
思路参考后端分层:Controller(业务) / Repository(数据) / DTO(模型) / UI(页面) ,避免所有代码堆在lib下,Java团队很容易理解。
选用:Provider 做状态管理(入门友好,企业内部项目够用,不引入过重Bloc)
适用场景:车辆列表、实时告警、工单、登录、报表,对接SpringBoot HTTP / WebSocket
lib/
├── main.dart # 程序入口,全局初始化、路由注册
├── common/ # 公共基础(类似common包)
│ ├── constants.dart # 常量:后端接口地址、超时时间、枚举
│ ├── http_client.dart # 统一http请求封装(拦截器、token、异常捕获,相当于Spring的RestTemplate封装)
│ ├── ws_client.dart # WebSocket全局客户端(接收车辆告警推送)
│ └── utils/ # 工具类:日期格式化、hex转换、字典映射
├── dto/ # 数据传输对象【重点!和后端SpringBoot DTO一一对应】
│ ├── bus_dto.dart # 车辆DTO:busId, plateNo, lat, lon, status
│ ├── alarm_dto.dart # 告警DTO:alarmId, busId, alarmType, alarmTime, content
│ └── login_dto.dart # 登录请求/响应DTO
├── repository/ # 数据仓库层(Repository,DAO层)
│ ├── bus_repository.dart # 车辆数据:getBusList()、getBusDetail()
│ ├── alarm_repository.dart# 告警数据:getAlarmList()、subscribeAlarm()(websocket订阅)
│ └── auth_repository.dart # 登录、刷新token
├── provider/ # 状态层(业务状态,类似后端service,给页面提供数据)
│ ├── bus_provider.dart
│ ├── alarm_provider.dart # 实时告警状态,页面监听自动刷新UI
│ └── auth_provider.dart # 登录状态、token保存
├── pages/ # UI页面层(Controller视图层)
│ ├── login/
│ │ └── login_page.dart
│ ├── home/
│ │ └── home_page.dart # 首页:车辆概览
│ ├── bus/
│ │ ├── bus_list_page.dart # 车辆列表页
│ │ └── bus_detail_page.dart# 单车详情
│ └── alarm/
│ ├── alarm_list_page.dart # 告警列表
│ └── alarm_detail_page.dart
├── widgets/ # 公共组件(复用UI控件,封装自定义组件)
│ ├── bus_card.dart # 车辆卡片组件,多处页面复用
│ ├── alarm_item.dart # 告警列表条目组件
│ └── common_empty.dart # 空数据占位组件
└── routes/ # 路由管理(页面跳转,统一注册)
└── app_routes.dart
分层职责(类比SpringBoot,方便团队理解)
| Flutter 分层 | SpringBoot 对应模块 | 职责 |
|---|---|---|
| DTO | DTO/Entity | 定义JSON实体,前后端字段一一对应 |
| Repository | Mapper + DAO | 只做数据获取:http请求、websocket收发,不处理业务逻辑 |
| Provider | Service | 调用Repository,维护页面状态、缓存数据,业务逻辑在这里 |
| Pages | Controller | UI页面,监听Provider状态,渲染页面、绑定点击事件 |
| common/http_client | RestTemplate/Feign | 统一请求封装,token拦截、统一错误处理 |
代码片段示例(分层演示)
1. pubspec.yaml 依赖
name: bus_operation
description: 公交运维APP
version: 1.0.0+1
environment:
sdk: '>=3.4.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
http: ^1.2.1
provider: ^6.1.1
web_socket_channel: ^2.4.0
shared_preferences: ^2.2.3 # 本地存储token
convert: ^3.1.1 # 可选,hex/编码处理(你们车机报文会用到)
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
执行拉包:
flutter pub get
2. dto/alarm_dto.dart(和后端JSON对齐)
class AlarmDto {
final String alarmId;
final String busId;
final String plateNo;
final int alarmType;
final String content;
final String alarmTime;
AlarmDto({
required this.alarmId,
required this.busId,
required this.plateNo,
required this.alarmType,
required this.content,
required this.alarmTime,
});
// json -> 对象,类似Jackson反序列化
factory AlarmDto.fromJson(Map<String, dynamic> json) {
return AlarmDto(
alarmId: json['alarmId'],
busId: json['busId'],
plateNo: json['plateNo'],
alarmType: json['alarmType'],
content: json['content'],
alarmTime: json['alarmTime'],
);
}
}
3. repository/alarm_repository.dart(DAO层,只拿数据)
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../dto/alarm_dto.dart';
import '../common/http_client.dart';
class AlarmRepository {
final HttpClient _http = HttpClient();
// 查询历史告警列表
Future<List<AlarmDto>> getAlarmList() async {
final resp = await _http.get("/api/alarm/list");
List<dynamic> list = resp['data'];
return list.map((item) => AlarmDto.fromJson(item)).toList();
}
}
4. provider/alarm_provider.dart(Service层,业务+状态)
import 'package:flutter/foundation.dart';
import '../dto/alarm_dto.dart';
import '../repository/alarm_repository.dart';
class AlarmProvider extends ChangeNotifier {
final AlarmRepository _repo = AlarmRepository();
List<AlarmDto> alarmList = [];
bool loading = false;
// 加载告警列表
Future<void> loadAlarmList() async {
loading = true;
notifyListeners(); // 通知UI刷新,类似前端发布事件
try {
alarmList = await _repo.getAlarmList();
} catch (e) {
debugPrint("加载告警失败:$e");
} finally {
loading = false;
notifyListeners();
}
}
// 新增实时告警(WebSocket收到推送调用)
void addRealTimeAlarm(AlarmDto alarm) {
alarmList.insert(0, alarm); // 新告警放最顶部
notifyListeners();
}
}
5. pages/alarm/alarm_list_page.dart(UI页面)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../provider/alarm_provider.dart';
import '../../widgets/alarm_item.dart';
class AlarmListPage extends StatefulWidget {
const AlarmListPage({super.key});
@override
State<AlarmListPage> createState() => _AlarmListPageState();
}
class _AlarmListPageState extends State<AlarmListPage> {
@override
void initState() {
super.initState();
// 页面初始化加载数据
Provider.of<AlarmProvider>(context, listen: false).loadAlarmList();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("车辆告警列表")),
body: Consumer<AlarmProvider>(
builder: (ctx, alarmProvider, child) {
if (alarmProvider.loading) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: alarmProvider.alarmList.length,
itemBuilder: (ctx, index) {
var alarm = alarmProvider.alarmList[index];
return AlarmItem(alarm: alarm);
},
);
},
),
);
}
}
6. common/http_client.dart 全局Http封装(拦截token,统一异常)
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'constants.dart';
class HttpClient {
final String baseUrl = Constants.baseUrl;
Future<Map<String, dynamic>> get(String path) async {
final uri = Uri.parse("$baseUrl$path");
final resp = await http.get(uri, headers: {
"Content-Type": "application/json",
"token": "xxx", // 从auth provider读取token
});
if (resp.statusCode == 200) {
return jsonDecode(resp.body);
} else {
throw Exception("接口请求异常");
}
}
}
7. widgets/alarm_item.dart 复用UI组件
import 'package:flutter/material.dart';
import '../../dto/alarm_dto.dart';
class AlarmItem extends StatelessWidget {
final AlarmDto alarm;
const AlarmItem({super.key, required this.alarm});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal:12, vertical:6),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("车牌:${alarm.plateNo}",style: TextStyle(fontSize:16,fontWeight: FontWeight.bold)),
SizedBox(height:4),
Text("告警内容:${alarm.content}"),
Text("告警时间:${alarm.alarmTime}"),
],
),
),
);
}
}
main.dart 入口,全局注册Provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider/alarm_provider.dart';
import 'pages/alarm/alarm_list_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AlarmProvider()),
],
child: MaterialApp(
title: '公交运维',
theme: ThemeData(primarySwatch: Colors.blue),
home: const AlarmListPage(),
),
);
}
}
开发规范建议(适配Java团队)
- DTO字段名和后端JSON完全一致,减少前后端联调坑;
- Repository 禁止写UI相关代码,只负责网络/websocket;
- Provider只管理状态、业务逻辑,不写复杂Widget;
- 页面组件拆到
widgets目录,不要在Page里面写一大堆嵌套布局; - WebSocket放在common全局单例,收到消息调用provider.addRealTimeAlarm(),推送实时告警。