From 778726f943974d90e0a6d7299e369a99b89364fe Mon Sep 17 00:00:00 2001 From: bot_pm Date: Sun, 14 Jun 2026 13:19:18 +0800 Subject: [PATCH] =?UTF-8?q?Phase=201=20#24:=20Flutter=20=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=AB=AF=E6=A1=86=E6=9E=B6=EF=BC=88=E4=B8=89=E5=90=88=E4=B8=80?= =?UTF-8?q?=20APP=20=E9=AA=A8=E6=9E=B6=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- mobile/lib/main.dart | 25 +++++++++++++++ mobile/lib/pages/home/home_page.dart | 35 ++++++++++++++++++++ mobile/lib/pages/login/login_page.dart | 44 ++++++++++++++++++++++++++ mobile/lib/services/auth_service.dart | 33 +++++++++++++++++++ mobile/pubspec.yaml | 22 +++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 mobile/lib/main.dart create mode 100644 mobile/lib/pages/home/home_page.dart create mode 100644 mobile/lib/pages/login/login_page.dart create mode 100644 mobile/lib/services/auth_service.dart create mode 100644 mobile/pubspec.yaml diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart new file mode 100644 index 00000000..8a366eb8 --- /dev/null +++ b/mobile/lib/main.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'services/auth_service.dart'; +import 'pages/login/login_page.dart'; +import 'pages/home/home_page.dart'; + +void main() => runApp(const WaterApp()); + +class WaterApp extends StatelessWidget { + const WaterApp({super.key}); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ChangeNotifierProvider(create: (_) => AuthService())], + child: MaterialApp( + title: '智慧水务', + theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), + home: Consumer( + builder: (_, auth, __) => auth.isLoggedIn ? const HomePage() : const LoginPage(), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/home/home_page.dart b/mobile/lib/pages/home/home_page.dart new file mode 100644 index 00000000..3f23e9a0 --- /dev/null +++ b/mobile/lib/pages/home/home_page.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../services/auth_service.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + @override State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _tabIndex = 0; + + @override + Widget build(BuildContext context) { + final auth = context.read(); + return Scaffold( + appBar: AppBar(title: const Text('智慧水务'), actions: [ + IconButton(icon: const Icon(Icons.logout), onPressed: () async { await auth.logout(); Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomePage())); }) + ]), + body: IndexedStack(index: _tabIndex, children: const [ + Center(child: Text('💧 供水管理')), + Center(child: Text('🔍 巡检管理')), + Center(child: Text('💰 营业收费')), + ]), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _tabIndex, onTap: (i) => setState(() => _tabIndex = i), + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.water), label: '供水'), + BottomNavigationBarItem(icon: Icon(Icons.search), label: '巡检'), + BottomNavigationBarItem(icon: Icon(Icons.receipt_long), label: '营收'), + ], + ), + ); + } +} diff --git a/mobile/lib/pages/login/login_page.dart b/mobile/lib/pages/login/login_page.dart new file mode 100644 index 00000000..117ddbda --- /dev/null +++ b/mobile/lib/pages/login/login_page.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../services/auth_service.dart'; +import '../home/home_page.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + @override State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + final _userCtrl = TextEditingController(text: 'admin'); + final _passCtrl = TextEditingController(text: 'admin123'); + bool _loading = false; + + Future _login() async { + setState(() => _loading = true); + final ok = await context.read().login(_userCtrl.text, _passCtrl.text); + if (!mounted) return; + setState(() => _loading = false); + if (ok) { + Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const HomePage())); + } else { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('登录失败'))); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Padding(padding: const EdgeInsets.all(32), child: Column(mainAxisSize: MainAxisSize.min, children: [ + const Text('智慧水务', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.blue)), + const SizedBox(height: 32), + TextField(controller: _userCtrl, decoration: const InputDecoration(labelText: '用户名', prefixIcon: Icon(Icons.person))), + const SizedBox(height: 16), + TextField(controller: _passCtrl, obscureText: true, decoration: const InputDecoration(labelText: '密码', prefixIcon: Icon(Icons.lock))), + const SizedBox(height: 24), + SizedBox(width: double.infinity, height: 48, child: ElevatedButton(onPressed: _loading ? null : _login, child: Text(_loading ? '登录中...' : '登录'))), + ])), + ), + ); + } +} diff --git a/mobile/lib/services/auth_service.dart b/mobile/lib/services/auth_service.dart new file mode 100644 index 00000000..5ed0f34c --- /dev/null +++ b/mobile/lib/services/auth_service.dart @@ -0,0 +1,33 @@ +import 'package:flutter/foundation.dart'; +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class AuthService extends ChangeNotifier { + String _token = ''; + bool get isLoggedIn => _token.isNotEmpty; + + final Dio _dio = Dio(BaseOptions(baseUrl: 'http://10.0.2.2:8080/api/base')); + + Future login(String username, String password) async { + try { + final res = await _dio.post('/auth/login', data: {'username': username, 'password': password}); + if (res.data['code'] == 200) { + _token = res.data['data']; + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('token', _token); + notifyListeners(); + return true; + } + } catch (e) { + debugPrint('Login failed: $e'); + } + return false; + } + + Future logout() async { + _token = ''; + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('token'); + notifyListeners(); + } +} diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml new file mode 100644 index 00000000..4386a611 --- /dev/null +++ b/mobile/pubspec.yaml @@ -0,0 +1,22 @@ +name: water_management +description: 智慧水务管理系统 - 移动端 +version: 1.0.0 + +environment: + sdk: '>=3.2.0 <4.0.0' + +dependencies: + flutter: { sdk: flutter } + dio: ^5.4.0 + provider: ^6.1.0 + shared_preferences: ^2.2.0 + flutter_map: ^6.1.0 + latlong2: ^0.9.0 + geolocator: ^11.0.0 + image_picker: ^1.0.0 + permission_handler: ^11.0.0 + flutter_local_notifications: ^17.0.0 + +dev_dependencies: + flutter_test: { sdk: flutter } + flutter_lints: ^3.0.0