chore: 全面规范化代码,遵循 CLAUDE.md 编码规范
- C# 文件命名改为 snake_case(28 个文件重命名) - C# 类转换为主构造函数(8 个类) - 空 catch 添加异常类型(2 处) - 新建 GlobalUsings.cs(Health.Infrastructure、Health.WebApi) - Flutter 移除 go_router,改用 Riverpod 路由栈 - Flutter 移除 flutter_secure_storage,改用 sqflite 持久化 - 修复 Flutter 构建路径(Flutter SDK 迁至 D 盘) - 后端端口改为 0.0.0.0:5000,支持局域网访问
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'secure_storage.dart';
|
||||
import 'local_database.dart';
|
||||
|
||||
/// API 基础地址
|
||||
const String baseUrl = 'http://10.4.172.93:5000';
|
||||
const String baseUrl = 'http://10.4.185.103:5000';
|
||||
|
||||
/// Dio HTTP 客户端封装——带 token 注入、401 自动刷新
|
||||
class ApiClient {
|
||||
final Dio _dio;
|
||||
final SecureStorage _storage;
|
||||
final LocalDatabase _db;
|
||||
|
||||
ApiClient({required SecureStorage storage})
|
||||
: _storage = storage,
|
||||
ApiClient({required LocalDatabase db})
|
||||
: _db = db,
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
@@ -23,16 +23,16 @@ class ApiClient {
|
||||
|
||||
Dio get dio => _dio;
|
||||
|
||||
Future<String?> get accessToken => _storage.readAccessToken();
|
||||
Future<String?> get refreshToken => _storage.readRefreshToken();
|
||||
Future<String?> get accessToken => _db.read('access_token');
|
||||
Future<String?> get refreshToken => _db.read('refresh_token');
|
||||
|
||||
Future<void> saveTokens(String access, String refresh) async {
|
||||
await _storage.writeAccessToken(access);
|
||||
await _storage.writeRefreshToken(refresh);
|
||||
await _db.write('access_token', access);
|
||||
await _db.write('refresh_token', refresh);
|
||||
}
|
||||
|
||||
Future<void> clearTokens() async {
|
||||
await _storage.deleteAll();
|
||||
await _db.deleteAll();
|
||||
}
|
||||
|
||||
/// 带 token 的 GET 请求
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'navigation_provider.dart';
|
||||
import '../pages/auth/login_page.dart';
|
||||
import '../pages/home/home_page.dart';
|
||||
import '../pages/chart/trend_page.dart';
|
||||
@@ -9,49 +10,51 @@ import '../pages/settings/settings_pages.dart';
|
||||
import '../pages/profile/profile_page.dart';
|
||||
import '../pages/remaining_pages.dart';
|
||||
|
||||
/// 应用路由配置
|
||||
class AppRouter {
|
||||
AppRouter._();
|
||||
|
||||
static final GoRouter router = GoRouter(
|
||||
initialLocation: '/login',
|
||||
routes: [
|
||||
GoRoute(path: '/login', builder: (_, _) => const LoginPage()),
|
||||
GoRoute(path: '/home', builder: (_, _) => const HomePage()),
|
||||
GoRoute(path: '/trend/:type', builder: (_, state) => TrendPage(metricType: state.pathParameters['type']!)),
|
||||
GoRoute(path: '/calendar', builder: (_, _) => const HealthCalendarPage()),
|
||||
|
||||
// 用药
|
||||
GoRoute(path: '/medications', builder: (_, _) => const MedicationListPage()),
|
||||
GoRoute(path: '/medications/add', builder: (_, _) => const MedicationEditPage()),
|
||||
GoRoute(path: '/medications/:id/edit', builder: (_, state) => MedicationEditPage(id: state.pathParameters['id'])),
|
||||
|
||||
// 报告
|
||||
GoRoute(path: '/reports', builder: (_, _) => const ReportListPage()),
|
||||
GoRoute(path: '/reports/:id', builder: (_, state) => ReportDetailPage(id: state.pathParameters['id']!)),
|
||||
|
||||
// 问诊
|
||||
GoRoute(path: '/doctors', builder: (_, _) => const DoctorListPage()),
|
||||
GoRoute(path: '/consultation/:id', builder: (_, state) => DoctorChatPage(id: state.pathParameters['id']!)),
|
||||
|
||||
// 运动
|
||||
GoRoute(path: '/exercise-plan', builder: (_, _) => const ExercisePlanPage()),
|
||||
|
||||
// 饮食
|
||||
GoRoute(path: '/diet-records', builder: (_, _) => const DietRecordListPage()),
|
||||
|
||||
// 个人中心
|
||||
GoRoute(path: '/profile', builder: (_, _) => const ProfilePage()),
|
||||
GoRoute(path: '/profile/edit', builder: (_, _) => const EditProfilePage()),
|
||||
GoRoute(path: '/health-archive', builder: (_, _) => const HealthArchivePage()),
|
||||
|
||||
// 复查
|
||||
GoRoute(path: '/followups', builder: (_, _) => const FollowUpListPage()),
|
||||
|
||||
// 设置
|
||||
GoRoute(path: '/settings', builder: (_, _) => const SettingsPage()),
|
||||
GoRoute(path: '/settings/notifications', builder: (_, _) => const NotificationPrefsPage()),
|
||||
GoRoute(path: '/page/:type', builder: (_, state) => StaticTextPage(type: state.pathParameters['type']!)),
|
||||
],
|
||||
);
|
||||
/// 根据路由信息返回对应页面
|
||||
Widget buildPage(RouteInfo route) {
|
||||
final params = route.params;
|
||||
switch (route.name) {
|
||||
case 'login':
|
||||
return const LoginPage();
|
||||
case 'home':
|
||||
return const HomePage();
|
||||
case 'trend':
|
||||
return TrendPage(metricType: params['type'] ?? '');
|
||||
case 'calendar':
|
||||
return const HealthCalendarPage();
|
||||
case 'medications':
|
||||
return const MedicationListPage();
|
||||
case 'medicationAdd':
|
||||
return const MedicationEditPage();
|
||||
case 'medicationEdit':
|
||||
return MedicationEditPage(id: params['id']);
|
||||
case 'reports':
|
||||
return const ReportListPage();
|
||||
case 'reportDetail':
|
||||
return ReportDetailPage(id: params['id']!);
|
||||
case 'doctors':
|
||||
return const DoctorListPage();
|
||||
case 'consultation':
|
||||
return DoctorChatPage(id: params['id']!);
|
||||
case 'exercisePlan':
|
||||
return const ExercisePlanPage();
|
||||
case 'dietRecords':
|
||||
return const DietRecordListPage();
|
||||
case 'profile':
|
||||
return const ProfilePage();
|
||||
case 'profileEdit':
|
||||
return const EditProfilePage();
|
||||
case 'healthArchive':
|
||||
return const HealthArchivePage();
|
||||
case 'followups':
|
||||
return const FollowUpListPage();
|
||||
case 'settings':
|
||||
return const SettingsPage();
|
||||
case 'notificationPrefs':
|
||||
return const NotificationPrefsPage();
|
||||
case 'staticText':
|
||||
return StaticTextPage(type: params['type']!);
|
||||
default:
|
||||
return const LoginPage();
|
||||
}
|
||||
}
|
||||
|
||||
58
health_app/lib/core/local_database.dart
Normal file
58
health_app/lib/core/local_database.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
/// SQLite 本地数据库——存储 token 等关键信息
|
||||
class LocalDatabase {
|
||||
static LocalDatabase? _instance;
|
||||
Database? _db;
|
||||
|
||||
LocalDatabase._();
|
||||
|
||||
static LocalDatabase get instance => _instance ??= LocalDatabase._();
|
||||
|
||||
Future<Database> get database async {
|
||||
_db ??= await _initDb();
|
||||
return _db!;
|
||||
}
|
||||
|
||||
Future<Database> _initDb() async {
|
||||
final dbPath = await getDatabasesPath();
|
||||
final path = join(dbPath, 'health_app.db');
|
||||
return openDatabase(
|
||||
path,
|
||||
version: 1,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute(
|
||||
'CREATE TABLE kv_store (key TEXT PRIMARY KEY, value TEXT)',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> write(String key, String value) async {
|
||||
final db = await database;
|
||||
await db.insert(
|
||||
'kv_store',
|
||||
{'key': key, 'value': value},
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> read(String key) async {
|
||||
final db = await database;
|
||||
final result =
|
||||
await db.query('kv_store', where: 'key = ?', whereArgs: [key]);
|
||||
if (result.isEmpty) return null;
|
||||
return result.first['value'] as String?;
|
||||
}
|
||||
|
||||
Future<void> delete(String key) async {
|
||||
final db = await database;
|
||||
await db.delete('kv_store', where: 'key = ?', whereArgs: [key]);
|
||||
}
|
||||
|
||||
Future<void> deleteAll() async {
|
||||
final db = await database;
|
||||
await db.delete('kv_store');
|
||||
}
|
||||
}
|
||||
56
health_app/lib/core/navigation_provider.dart
Normal file
56
health_app/lib/core/navigation_provider.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
/// 路由信息
|
||||
class RouteInfo {
|
||||
final String name;
|
||||
final Map<String, String> params;
|
||||
const RouteInfo(this.name, {this.params = const {}});
|
||||
|
||||
String param(String key) => params[key] ?? '';
|
||||
}
|
||||
|
||||
/// 路由栈 Notifier
|
||||
class RouteStackNotifier extends Notifier<List<RouteInfo>> {
|
||||
@override
|
||||
List<RouteInfo> build() => [const RouteInfo('login')];
|
||||
|
||||
void replace(String name, {Map<String, String> params = const {}}) {
|
||||
state = [RouteInfo(name, params: params)];
|
||||
}
|
||||
|
||||
void push(String name, {Map<String, String> params = const {}}) {
|
||||
state = [...state, RouteInfo(name, params: params)];
|
||||
}
|
||||
|
||||
void pop() {
|
||||
if (state.length > 1) {
|
||||
state = state.sublist(0, state.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 路由栈 Provider
|
||||
final routeStackProvider =
|
||||
NotifierProvider<RouteStackNotifier, List<RouteInfo>>(RouteStackNotifier.new);
|
||||
|
||||
/// 当前路由
|
||||
final currentRouteProvider = Provider<RouteInfo>((ref) {
|
||||
final stack = ref.watch(routeStackProvider);
|
||||
return stack.last;
|
||||
});
|
||||
|
||||
/// 跳转(替换整个栈)
|
||||
void goRoute(WidgetRef ref, String name, {Map<String, String> params = const {}}) {
|
||||
ref.read(routeStackProvider.notifier).replace(name, params: params);
|
||||
}
|
||||
|
||||
/// 推入新页面
|
||||
void pushRoute(WidgetRef ref, String name, {Map<String, String> params = const {}}) {
|
||||
ref.read(routeStackProvider.notifier).push(name, params: params);
|
||||
}
|
||||
|
||||
/// 返回上一页
|
||||
void popRoute(WidgetRef ref) {
|
||||
ref.read(routeStackProvider.notifier).pop();
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
/// Token 安全存储(iOS Keychain / Android EncryptedSharedPreferences / Web 内存)
|
||||
class SecureStorage {
|
||||
final FlutterSecureStorage _storage;
|
||||
static final Map<String, String> _webFallback = {};
|
||||
|
||||
SecureStorage() : _storage = const FlutterSecureStorage();
|
||||
static const _access = 'access_token';
|
||||
static const _refresh = 'refresh_token';
|
||||
|
||||
bool get _isWeb => kIsWeb;
|
||||
|
||||
Future<void> writeAccessToken(String t) async {
|
||||
if (_isWeb) { _webFallback[_access] = t; return; }
|
||||
await _storage.write(key: _access, value: t);
|
||||
}
|
||||
Future<String?> readAccessToken() async {
|
||||
if (_isWeb) return _webFallback[_access];
|
||||
return _storage.read(key: _access);
|
||||
}
|
||||
Future<void> writeRefreshToken(String t) async {
|
||||
if (_isWeb) { _webFallback[_refresh] = t; return; }
|
||||
await _storage.write(key: _refresh, value: t);
|
||||
}
|
||||
Future<String?> readRefreshToken() async {
|
||||
if (_isWeb) return _webFallback[_refresh];
|
||||
return _storage.read(key: _refresh);
|
||||
}
|
||||
Future<void> deleteAll() async {
|
||||
if (_isWeb) { _webFallback.clear(); return; }
|
||||
await _storage.deleteAll();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user