Kaynağa Gözat

Phase 1 #24: Flutter 移动端框架(三合一 APP 骨架)

- pubspec.yaml: dio/provider/shared_preferences/flutter_map/geolocator
- main.dart: Provider 状态管理 + 登录守卫
- AuthService: Token 管理 + Dio HTTP + SharedPreferences 持久化
- LoginPage: Material Design 登录页
- HomePage: 三合一 BottomTab 导航(供水/巡检/营收)
- 预留依赖: flutter_local_notifications/image_picker/permission_handler
bot_pm 5 gün önce
ebeveyn
işleme
778726f943

+ 25
- 0
mobile/lib/main.dart Dosyayı Görüntüle

@@ -0,0 +1,25 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import 'services/auth_service.dart';
4
+import 'pages/login/login_page.dart';
5
+import 'pages/home/home_page.dart';
6
+
7
+void main() => runApp(const WaterApp());
8
+
9
+class WaterApp extends StatelessWidget {
10
+  const WaterApp({super.key});
11
+
12
+  @override
13
+  Widget build(BuildContext context) {
14
+    return MultiProvider(
15
+      providers: [ChangeNotifierProvider(create: (_) => AuthService())],
16
+      child: MaterialApp(
17
+        title: '智慧水务',
18
+        theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
19
+        home: Consumer<AuthService>(
20
+          builder: (_, auth, __) => auth.isLoggedIn ? const HomePage() : const LoginPage(),
21
+        ),
22
+      ),
23
+    );
24
+  }
25
+}

+ 35
- 0
mobile/lib/pages/home/home_page.dart Dosyayı Görüntüle

@@ -0,0 +1,35 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../services/auth_service.dart';
4
+
5
+class HomePage extends StatefulWidget {
6
+  const HomePage({super.key});
7
+  @override State<HomePage> createState() => _HomePageState();
8
+}
9
+
10
+class _HomePageState extends State<HomePage> {
11
+  int _tabIndex = 0;
12
+
13
+  @override
14
+  Widget build(BuildContext context) {
15
+    final auth = context.read<AuthService>();
16
+    return Scaffold(
17
+      appBar: AppBar(title: const Text('智慧水务'), actions: [
18
+        IconButton(icon: const Icon(Icons.logout), onPressed: () async { await auth.logout(); Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomePage())); })
19
+      ]),
20
+      body: IndexedStack(index: _tabIndex, children: const [
21
+        Center(child: Text('💧 供水管理')),
22
+        Center(child: Text('🔍 巡检管理')),
23
+        Center(child: Text('💰 营业收费')),
24
+      ]),
25
+      bottomNavigationBar: BottomNavigationBar(
26
+        currentIndex: _tabIndex, onTap: (i) => setState(() => _tabIndex = i),
27
+        items: const [
28
+          BottomNavigationBarItem(icon: Icon(Icons.water), label: '供水'),
29
+          BottomNavigationBarItem(icon: Icon(Icons.search), label: '巡检'),
30
+          BottomNavigationBarItem(icon: Icon(Icons.receipt_long), label: '营收'),
31
+        ],
32
+      ),
33
+    );
34
+  }
35
+}

+ 44
- 0
mobile/lib/pages/login/login_page.dart Dosyayı Görüntüle

@@ -0,0 +1,44 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:provider/provider.dart';
3
+import '../../services/auth_service.dart';
4
+import '../home/home_page.dart';
5
+
6
+class LoginPage extends StatefulWidget {
7
+  const LoginPage({super.key});
8
+  @override State<LoginPage> createState() => _LoginPageState();
9
+}
10
+
11
+class _LoginPageState extends State<LoginPage> {
12
+  final _userCtrl = TextEditingController(text: 'admin');
13
+  final _passCtrl = TextEditingController(text: 'admin123');
14
+  bool _loading = false;
15
+
16
+  Future<void> _login() async {
17
+    setState(() => _loading = true);
18
+    final ok = await context.read<AuthService>().login(_userCtrl.text, _passCtrl.text);
19
+    if (!mounted) return;
20
+    setState(() => _loading = false);
21
+    if (ok) {
22
+      Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomePage()));
23
+    } else {
24
+      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('登录失败')));
25
+    }
26
+  }
27
+
28
+  @override
29
+  Widget build(BuildContext context) {
30
+    return Scaffold(
31
+      body: Center(
32
+        child: Padding(padding: const EdgeInsets.all(32), child: Column(mainAxisSize: MainAxisSize.min, children: [
33
+          const Text('智慧水务', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue)),
34
+          const SizedBox(height: 32),
35
+          TextField(controller: _userCtrl, decoration: const InputDecoration(labelText: '用户名', prefixIcon: Icon(Icons.person))),
36
+          const SizedBox(height: 16),
37
+          TextField(controller: _passCtrl, obscureText: true, decoration: const InputDecoration(labelText: '密码', prefixIcon: Icon(Icons.lock))),
38
+          const SizedBox(height: 24),
39
+          SizedBox(width: double.infinity, height: 48, child: ElevatedButton(onPressed: _loading ? null : _login, child: Text(_loading ? '登录中...' : '登录'))),
40
+        ])),
41
+      ),
42
+    );
43
+  }
44
+}

+ 33
- 0
mobile/lib/services/auth_service.dart Dosyayı Görüntüle

@@ -0,0 +1,33 @@
1
+import 'package:flutter/foundation.dart';
2
+import 'package:dio/dio.dart';
3
+import 'package:shared_preferences/shared_preferences.dart';
4
+
5
+class AuthService extends ChangeNotifier {
6
+  String _token = '';
7
+  bool get isLoggedIn => _token.isNotEmpty;
8
+
9
+  final Dio _dio = Dio(BaseOptions(baseUrl: 'http://10.0.2.2:8080/api/base'));
10
+
11
+  Future<bool> login(String username, String password) async {
12
+    try {
13
+      final res = await _dio.post('/auth/login', data: {'username': username, 'password': password});
14
+      if (res.data['code'] == 200) {
15
+        _token = res.data['data'];
16
+        final prefs = await SharedPreferences.getInstance();
17
+        await prefs.setString('token', _token);
18
+        notifyListeners();
19
+        return true;
20
+      }
21
+    } catch (e) {
22
+      debugPrint('Login failed: $e');
23
+    }
24
+    return false;
25
+  }
26
+
27
+  Future<void> logout() async {
28
+    _token = '';
29
+    final prefs = await SharedPreferences.getInstance();
30
+    await prefs.remove('token');
31
+    notifyListeners();
32
+  }
33
+}

+ 22
- 0
mobile/pubspec.yaml Dosyayı Görüntüle

@@ -0,0 +1,22 @@
1
+name: water_management
2
+description: 智慧水务管理系统 - 移动端
3
+version: 1.0.0
4
+
5
+environment:
6
+  sdk: '>=3.2.0 <4.0.0'
7
+
8
+dependencies:
9
+  flutter: { sdk: flutter }
10
+  dio: ^5.4.0
11
+  provider: ^6.1.0
12
+  shared_preferences: ^2.2.0
13
+  flutter_map: ^6.1.0
14
+  latlong2: ^0.9.0
15
+  geolocator: ^11.0.0
16
+  image_picker: ^1.0.0
17
+  permission_handler: ^11.0.0
18
+  flutter_local_notifications: ^17.0.0
19
+
20
+dev_dependencies:
21
+  flutter_test: { sdk: flutter }
22
+  flutter_lints: ^3.0.0