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
This commit is contained in:
@@ -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<AuthService>(
|
||||||
|
builder: (_, auth, __) => auth.isLoggedIn ? const HomePage() : const LoginPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<HomePage> createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage> {
|
||||||
|
int _tabIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final auth = context.read<AuthService>();
|
||||||
|
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: '营收'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LoginPage> createState() => _LoginPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginPageState extends State<LoginPage> {
|
||||||
|
final _userCtrl = TextEditingController(text: 'admin');
|
||||||
|
final _passCtrl = TextEditingController(text: 'admin123');
|
||||||
|
bool _loading = false;
|
||||||
|
|
||||||
|
Future<void> _login() async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
final ok = await context.read<AuthService>().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 ? '登录中...' : '登录'))),
|
||||||
|
])),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<bool> 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<void> logout() async {
|
||||||
|
_token = '';
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove('token');
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user