feat: UI 系统中心化 + 趋势图重构 + 法律文档 H5 上线
- UI 系统: 新增 design_tokens / module_visuals / app_buttons / app_status_badge / app_toast / ai_content 六个共享模块; 28 个页面接入, SnackBar 全部替换为 AppToast - 趋势图: 直线折线 + 渐变填充 + 阴影; 去网格线; X 轴日+月份分隔; Y 轴整数刻度最多 4 个; 点击数据点/录入记录显示上方浮层, 选中点变实心 - 法律文档 H5: 后端 wwwroot 托管隐私政策/服务协议/关于/个人信息收集清单/第三方 SDK 清单 + 索引页; Program.cs 启用 UseDefaultFiles + UseStaticFiles - 其他: auth_provider 登录流程调整; api_client IP 适配; 主页智能体栏间距优化
This commit is contained in:
@@ -6,7 +6,7 @@ import 'local_database.dart';
|
||||
/// API 基础地址。可通过 --dart-define=API_BASE_URL=http://host:5000 覆盖。
|
||||
const String baseUrl = String.fromEnvironment(
|
||||
'API_BASE_URL',
|
||||
defaultValue: 'http://192.168.10.139:5000',
|
||||
defaultValue: 'http://10.4.212.224:5000',
|
||||
);
|
||||
|
||||
class ApiException implements Exception {
|
||||
@@ -19,21 +19,46 @@ class ApiException implements Exception {
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
typedef AuthExpiredListener = void Function();
|
||||
|
||||
class AuthExpiredNotifier {
|
||||
final List<AuthExpiredListener> _listeners = [];
|
||||
|
||||
AuthExpiredListener addListener(AuthExpiredListener listener) {
|
||||
_listeners.add(listener);
|
||||
return () => _listeners.remove(listener);
|
||||
}
|
||||
|
||||
void notify() {
|
||||
for (final listener in List<AuthExpiredListener>.from(_listeners)) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dio HTTP 客户端封装——带 token 注入、401 自动刷新
|
||||
class ApiClient {
|
||||
final Dio _dio;
|
||||
final LocalDatabase _db;
|
||||
final AuthExpiredNotifier? _authExpiredNotifier;
|
||||
|
||||
ApiClient({required LocalDatabase db})
|
||||
: _db = db,
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
)) {
|
||||
ApiClient({
|
||||
required LocalDatabase db,
|
||||
AuthExpiredNotifier? authExpiredNotifier,
|
||||
}) : _db = db,
|
||||
_authExpiredNotifier = authExpiredNotifier,
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
),
|
||||
) {
|
||||
_dio.interceptors.add(_AuthInterceptor(this));
|
||||
_dio.interceptors.add(LogInterceptor(requestBody: false, responseBody: false));
|
||||
_dio.interceptors.add(
|
||||
LogInterceptor(requestBody: false, responseBody: false),
|
||||
);
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
@@ -51,8 +76,15 @@ class ApiClient {
|
||||
await _db.delete('refresh_token');
|
||||
}
|
||||
|
||||
void notifyAuthExpired() {
|
||||
_authExpiredNotifier?.notify();
|
||||
}
|
||||
|
||||
/// 带 token 的 GET 请求
|
||||
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
|
||||
Future<Response> get(
|
||||
String path, {
|
||||
Map<String, dynamic>? queryParameters,
|
||||
}) async {
|
||||
return _request(_dio.get(path, queryParameters: queryParameters));
|
||||
}
|
||||
|
||||
@@ -72,9 +104,16 @@ class ApiClient {
|
||||
}
|
||||
|
||||
/// 上传文件(multipart),返回文件 URL
|
||||
Future<String?> uploadFile(String path, File file, {String fieldName = 'file'}) async {
|
||||
Future<String?> uploadFile(
|
||||
String path,
|
||||
File file, {
|
||||
String fieldName = 'file',
|
||||
}) async {
|
||||
final formData = FormData.fromMap({
|
||||
fieldName: await MultipartFile.fromFile(file.path, filename: file.path.split('/').last),
|
||||
fieldName: await MultipartFile.fromFile(
|
||||
file.path,
|
||||
filename: file.path.split('/').last,
|
||||
),
|
||||
});
|
||||
final res = await _request(_dio.post(path, data: formData));
|
||||
final data = res.data;
|
||||
@@ -118,14 +157,15 @@ class ApiClient {
|
||||
return _ensureBusinessSuccess(await request);
|
||||
} on DioException catch (error) {
|
||||
final body = error.response?.data;
|
||||
final message = body is Map && body['message']?.toString().trim().isNotEmpty == true
|
||||
final message =
|
||||
body is Map && body['message']?.toString().trim().isNotEmpty == true
|
||||
? body['message'].toString()
|
||||
: error.type == DioExceptionType.connectionTimeout ||
|
||||
error.type == DioExceptionType.receiveTimeout
|
||||
? '请求超时,请稍后重试'
|
||||
: error.type == DioExceptionType.connectionError
|
||||
? '无法连接服务器,请检查网络或后端地址'
|
||||
: '请求失败,请稍后重试';
|
||||
error.type == DioExceptionType.receiveTimeout
|
||||
? '请求超时,请稍后重试'
|
||||
: error.type == DioExceptionType.connectionError
|
||||
? '无法连接服务器,请检查网络或后端地址'
|
||||
: '请求失败,请稍后重试';
|
||||
final rawCode = body is Map ? body['code'] : null;
|
||||
throw ApiException(
|
||||
message,
|
||||
@@ -144,7 +184,10 @@ class _AuthInterceptor extends Interceptor {
|
||||
_AuthInterceptor(this._client);
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
|
||||
void onRequest(
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
if (!options.path.contains('/auth/')) {
|
||||
final token = await _client.accessToken;
|
||||
if (token != null) {
|
||||
@@ -160,20 +203,26 @@ class _AuthInterceptor extends Interceptor {
|
||||
final refresh = await _client.refreshToken;
|
||||
if (refresh != null) {
|
||||
try {
|
||||
final response = await Dio(BaseOptions(baseUrl: baseUrl))
|
||||
.post('/api/auth/refresh', data: {'refreshToken': refresh});
|
||||
final response = await Dio(
|
||||
BaseOptions(baseUrl: baseUrl),
|
||||
).post('/api/auth/refresh', data: {'refreshToken': refresh});
|
||||
final data = response.data['data'];
|
||||
if (data != null) {
|
||||
await _client.saveTokens(data['accessToken'], data['refreshToken']);
|
||||
final opts = err.requestOptions;
|
||||
final token = data['accessToken'];
|
||||
opts.headers['Authorization'] = 'Bearer $token';
|
||||
final retryResponse = await Dio(BaseOptions(baseUrl: baseUrl)).fetch(opts);
|
||||
final retryResponse = await Dio(
|
||||
BaseOptions(baseUrl: baseUrl),
|
||||
).fetch(opts);
|
||||
return handler.resolve(retryResponse);
|
||||
}
|
||||
} catch (e) { log('[ApiClient] token刷新失败: $e'); }
|
||||
} catch (e) {
|
||||
log('[ApiClient] token刷新失败: $e');
|
||||
}
|
||||
}
|
||||
await _client.clearTokens();
|
||||
_client.notifyAuthExpired();
|
||||
}
|
||||
handler.next(err);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,37 @@ class AppColors {
|
||||
static const Color meadowAccent = Color(0xFFBAE6FD);
|
||||
static const Color sageAccent = Color(0xFFE9D5FF);
|
||||
|
||||
static const Color health = Color(0xFF3B82F6);
|
||||
static const Color healthLight = Color(0xFFEFF6FF);
|
||||
static const Color healthBorder = Color(0xFFBFDBFE);
|
||||
static const Color medication = Color(0xFF8B5CF6);
|
||||
static const Color medicationLight = Color(0xFFF5F3FF);
|
||||
static const Color medicationBorder = Color(0xFFDDD6FE);
|
||||
static const Color exercise = Color(0xFF10B981);
|
||||
static const Color exerciseLight = Color(0xFFECFDF5);
|
||||
static const Color exerciseBorder = Color(0xFFA7F3D0);
|
||||
static const Color report = Color(0xFF2563EB);
|
||||
static const Color reportLight = Color(0xFFEEF2FF);
|
||||
static const Color reportBorder = Color(0xFFC7D2FE);
|
||||
static const Color diet = Color(0xFFF97316);
|
||||
static const Color dietLight = Color(0xFFFFF7ED);
|
||||
static const Color dietBorder = Color(0xFFFED7AA);
|
||||
static const Color device = Color(0xFF06B6D4);
|
||||
static const Color deviceLight = Color(0xFFECFEFF);
|
||||
static const Color deviceBorder = Color(0xFFA5F3FC);
|
||||
static const Color notification = Color(0xFF7C5CFF);
|
||||
static const Color notificationLight = Color(0xFFF5F3FF);
|
||||
static const Color notificationBorder = Color(0xFFDDD6FE);
|
||||
static const Color doctor = Color(0xFF0F766E);
|
||||
static const Color doctorLight = Color(0xFFF0FDFA);
|
||||
static const Color doctorBorder = Color(0xFF99F6E4);
|
||||
static const Color calendar = Color(0xFF6366F1);
|
||||
static const Color calendarLight = Color(0xFFEEF2FF);
|
||||
static const Color calendarBorder = Color(0xFFC7D2FE);
|
||||
static const Color followup = Color(0xFFDB2777);
|
||||
static const Color followupLight = Color(0xFFFDF2F8);
|
||||
static const Color followupBorder = Color(0xFFFBCFE8);
|
||||
|
||||
static const Color background = Color(0xFFF8FAFF);
|
||||
static const Color backgroundSoft = Color(0xFFFFFFFF);
|
||||
static const Color cardBackground = Color(0xFFFFFFFF);
|
||||
@@ -131,6 +162,66 @@ class AppColors {
|
||||
colors: [Color(0xFF60A5FA), Color(0xFF8B5CF6)],
|
||||
);
|
||||
|
||||
static const LinearGradient healthGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF60A5FA), Color(0xFF2563EB)],
|
||||
);
|
||||
|
||||
static const LinearGradient medicationGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFA78BFA), Color(0xFF7C3AED)],
|
||||
);
|
||||
|
||||
static const LinearGradient exerciseGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF34D399), Color(0xFF059669)],
|
||||
);
|
||||
|
||||
static const LinearGradient reportGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF60A5FA), Color(0xFF2563EB)],
|
||||
);
|
||||
|
||||
static const LinearGradient dietGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFFB923C), Color(0xFFF97316)],
|
||||
);
|
||||
|
||||
static const LinearGradient deviceGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF22D3EE), Color(0xFF0891B2)],
|
||||
);
|
||||
|
||||
static const LinearGradient notificationGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF8B5CF6), Color(0xFF6366F1)],
|
||||
);
|
||||
|
||||
static const LinearGradient doctorCareGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF14B8A6), Color(0xFF0F766E)],
|
||||
);
|
||||
|
||||
static const LinearGradient calendarGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF818CF8), Color(0xFF4F46E5)],
|
||||
);
|
||||
|
||||
static const LinearGradient followupGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFF472B6), Color(0xFFDB2777)],
|
||||
);
|
||||
|
||||
static List<BoxShadow> get cardShadow => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF101828).withValues(alpha: 0.07),
|
||||
|
||||
171
health_app/lib/core/app_design_tokens.dart
Normal file
171
health_app/lib/core/app_design_tokens.dart
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app_colors.dart';
|
||||
|
||||
class AppSpacing {
|
||||
AppSpacing._();
|
||||
|
||||
static const double xs = 4;
|
||||
static const double sm = 8;
|
||||
static const double md = 12;
|
||||
static const double lg = 16;
|
||||
static const double xl = 20;
|
||||
static const double xxl = 24;
|
||||
|
||||
static const EdgeInsets page = EdgeInsets.fromLTRB(16, 12, 16, 24);
|
||||
static const EdgeInsets pageWithFab = EdgeInsets.fromLTRB(16, 12, 16, 88);
|
||||
static const EdgeInsets panel = EdgeInsets.all(16);
|
||||
static const EdgeInsets listItem = EdgeInsets.all(16);
|
||||
static const EdgeInsets sheet = EdgeInsets.fromLTRB(20, 18, 20, 24);
|
||||
static const EdgeInsets drawer = EdgeInsets.symmetric(horizontal: 12);
|
||||
}
|
||||
|
||||
class AppRadius {
|
||||
AppRadius._();
|
||||
|
||||
static const double xs = 6;
|
||||
static const double sm = 10;
|
||||
static const double md = 14;
|
||||
static const double lg = 16;
|
||||
static const double xl = 20;
|
||||
static const double card = 24;
|
||||
static const double pill = 999;
|
||||
|
||||
static BorderRadius get xsBorder => BorderRadius.circular(xs);
|
||||
static BorderRadius get smBorder => BorderRadius.circular(sm);
|
||||
static BorderRadius get mdBorder => BorderRadius.circular(md);
|
||||
static BorderRadius get lgBorder => BorderRadius.circular(lg);
|
||||
static BorderRadius get xlBorder => BorderRadius.circular(xl);
|
||||
static BorderRadius get cardBorder => BorderRadius.circular(card);
|
||||
static BorderRadius get pillBorder => BorderRadius.circular(pill);
|
||||
}
|
||||
|
||||
class AppShadows {
|
||||
AppShadows._();
|
||||
|
||||
static List<BoxShadow> get none => const [];
|
||||
|
||||
static List<BoxShadow> get soft => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF101828).withValues(alpha: 0.045),
|
||||
blurRadius: 14,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get panel => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF101828).withValues(alpha: 0.07),
|
||||
blurRadius: 22,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> get floating => [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF101828).withValues(alpha: 0.14),
|
||||
blurRadius: 28,
|
||||
offset: const Offset(0, 14),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class AppTextStyles {
|
||||
AppTextStyles._();
|
||||
|
||||
static const TextStyle appBarTitle = TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle summaryTitle = TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.2,
|
||||
);
|
||||
|
||||
static const TextStyle summarySubtitle = TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.35,
|
||||
);
|
||||
|
||||
static const TextStyle sectionTitle = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle listTitle = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.22,
|
||||
);
|
||||
|
||||
static const TextStyle primaryListTitle = TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.22,
|
||||
);
|
||||
|
||||
static const TextStyle listSubtitle = TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.35,
|
||||
);
|
||||
|
||||
static const TextStyle formLabel = TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textSecondary,
|
||||
);
|
||||
|
||||
static const TextStyle formValue = TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle tag = TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.15,
|
||||
);
|
||||
|
||||
static const TextStyle button = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
);
|
||||
|
||||
static const TextStyle miniButton = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
);
|
||||
|
||||
static const TextStyle chatBody = TextStyle(
|
||||
fontSize: 18,
|
||||
height: 1.5,
|
||||
color: AppColors.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle chatTitle = TextStyle(
|
||||
fontSize: 20,
|
||||
height: 1.35,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
);
|
||||
|
||||
static const TextStyle aiNote = TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.textHint,
|
||||
height: 1.25,
|
||||
);
|
||||
}
|
||||
|
||||
168
health_app/lib/core/app_module_visuals.dart
Normal file
168
health_app/lib/core/app_module_visuals.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'app_colors.dart';
|
||||
|
||||
enum AppModule {
|
||||
ai,
|
||||
health,
|
||||
medication,
|
||||
exercise,
|
||||
report,
|
||||
diet,
|
||||
device,
|
||||
notification,
|
||||
doctor,
|
||||
calendar,
|
||||
followup,
|
||||
}
|
||||
|
||||
class AppModuleVisual {
|
||||
final AppModule module;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final Color lightColor;
|
||||
final Color borderColor;
|
||||
final LinearGradient gradient;
|
||||
|
||||
const AppModuleVisual({
|
||||
required this.module,
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.lightColor,
|
||||
required this.borderColor,
|
||||
required this.gradient,
|
||||
});
|
||||
}
|
||||
|
||||
class AppModuleVisuals {
|
||||
AppModuleVisuals._();
|
||||
|
||||
static const ai = AppModuleVisual(
|
||||
module: AppModule.ai,
|
||||
label: 'AI',
|
||||
icon: LucideIcons.bot,
|
||||
color: AppColors.primary,
|
||||
lightColor: AppColors.primarySoft,
|
||||
borderColor: AppColors.primaryLight,
|
||||
gradient: AppColors.primaryGradient,
|
||||
);
|
||||
|
||||
static const health = AppModuleVisual(
|
||||
module: AppModule.health,
|
||||
label: '健康',
|
||||
icon: LucideIcons.heartPulse,
|
||||
color: AppColors.health,
|
||||
lightColor: AppColors.healthLight,
|
||||
borderColor: AppColors.healthBorder,
|
||||
gradient: AppColors.healthGradient,
|
||||
);
|
||||
|
||||
static const medication = AppModuleVisual(
|
||||
module: AppModule.medication,
|
||||
label: '用药',
|
||||
icon: LucideIcons.pill,
|
||||
color: AppColors.medication,
|
||||
lightColor: AppColors.medicationLight,
|
||||
borderColor: AppColors.medicationBorder,
|
||||
gradient: AppColors.medicationGradient,
|
||||
);
|
||||
|
||||
static const exercise = AppModuleVisual(
|
||||
module: AppModule.exercise,
|
||||
label: '运动',
|
||||
icon: LucideIcons.footprints,
|
||||
color: AppColors.exercise,
|
||||
lightColor: AppColors.exerciseLight,
|
||||
borderColor: AppColors.exerciseBorder,
|
||||
gradient: AppColors.exerciseGradient,
|
||||
);
|
||||
|
||||
static const report = AppModuleVisual(
|
||||
module: AppModule.report,
|
||||
label: '报告',
|
||||
icon: LucideIcons.fileText,
|
||||
color: AppColors.report,
|
||||
lightColor: AppColors.reportLight,
|
||||
borderColor: AppColors.reportBorder,
|
||||
gradient: AppColors.reportGradient,
|
||||
);
|
||||
|
||||
static const diet = AppModuleVisual(
|
||||
module: AppModule.diet,
|
||||
label: '饮食',
|
||||
icon: LucideIcons.utensils,
|
||||
color: AppColors.diet,
|
||||
lightColor: AppColors.dietLight,
|
||||
borderColor: AppColors.dietBorder,
|
||||
gradient: AppColors.dietGradient,
|
||||
);
|
||||
|
||||
static const device = AppModuleVisual(
|
||||
module: AppModule.device,
|
||||
label: '设备',
|
||||
icon: LucideIcons.bluetooth,
|
||||
color: AppColors.device,
|
||||
lightColor: AppColors.deviceLight,
|
||||
borderColor: AppColors.deviceBorder,
|
||||
gradient: AppColors.deviceGradient,
|
||||
);
|
||||
|
||||
static const notification = AppModuleVisual(
|
||||
module: AppModule.notification,
|
||||
label: '通知',
|
||||
icon: LucideIcons.bell,
|
||||
color: AppColors.notification,
|
||||
lightColor: AppColors.notificationLight,
|
||||
borderColor: AppColors.notificationBorder,
|
||||
gradient: AppColors.notificationGradient,
|
||||
);
|
||||
|
||||
static const doctor = AppModuleVisual(
|
||||
module: AppModule.doctor,
|
||||
label: '医生',
|
||||
icon: LucideIcons.stethoscope,
|
||||
color: AppColors.doctor,
|
||||
lightColor: AppColors.doctorLight,
|
||||
borderColor: AppColors.doctorBorder,
|
||||
gradient: AppColors.doctorCareGradient,
|
||||
);
|
||||
|
||||
static const calendar = AppModuleVisual(
|
||||
module: AppModule.calendar,
|
||||
label: '日历',
|
||||
icon: LucideIcons.calendarDays,
|
||||
color: AppColors.calendar,
|
||||
lightColor: AppColors.calendarLight,
|
||||
borderColor: AppColors.calendarBorder,
|
||||
gradient: AppColors.calendarGradient,
|
||||
);
|
||||
|
||||
static const followup = AppModuleVisual(
|
||||
module: AppModule.followup,
|
||||
label: '随访',
|
||||
icon: LucideIcons.calendarCheck,
|
||||
color: AppColors.followup,
|
||||
lightColor: AppColors.followupLight,
|
||||
borderColor: AppColors.followupBorder,
|
||||
gradient: AppColors.followupGradient,
|
||||
);
|
||||
|
||||
static const values = <AppModule, AppModuleVisual>{
|
||||
AppModule.ai: ai,
|
||||
AppModule.health: health,
|
||||
AppModule.medication: medication,
|
||||
AppModule.exercise: exercise,
|
||||
AppModule.report: report,
|
||||
AppModule.diet: diet,
|
||||
AppModule.device: device,
|
||||
AppModule.notification: notification,
|
||||
AppModule.doctor: doctor,
|
||||
AppModule.calendar: calendar,
|
||||
AppModule.followup: followup,
|
||||
};
|
||||
|
||||
static AppModuleVisual of(AppModule module) => values[module] ?? ai;
|
||||
}
|
||||
@@ -28,6 +28,18 @@ import '../pages/doctor/doctor_profile_page.dart';
|
||||
import '../pages/admin/admin_home_page.dart';
|
||||
import '../pages/admin/admin_add_doctor_page.dart';
|
||||
import '../providers/auth_provider.dart' show userRoleProvider;
|
||||
import '../widgets/app_error_state.dart';
|
||||
|
||||
Widget _missingParamPage() {
|
||||
return const Scaffold(
|
||||
body: AppErrorState(title: '页面参数错误', subtitle: '缺少打开页面所需的信息,请返回后重试'),
|
||||
);
|
||||
}
|
||||
|
||||
String? _requiredParam(Map<String, String> params, String key) {
|
||||
final value = params[key]?.trim();
|
||||
return value == null || value.isEmpty ? null : value;
|
||||
}
|
||||
|
||||
/// 根据路由信息返回对应页面
|
||||
Widget buildPage(RouteInfo route, WidgetRef ref) {
|
||||
@@ -59,7 +71,8 @@ Widget buildPage(RouteInfo route, WidgetRef ref) {
|
||||
case 'reports':
|
||||
return const ReportListPage();
|
||||
case 'aiAnalysis':
|
||||
return AiAnalysisPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : AiAnalysisPage(id: id);
|
||||
case 'reportOriginal':
|
||||
return ReportOriginalPage(
|
||||
url: params['url'] ?? '',
|
||||
@@ -82,13 +95,17 @@ Widget buildPage(RouteInfo route, WidgetRef ref) {
|
||||
case 'doctorSettings':
|
||||
return const DoctorSettingsPage();
|
||||
case 'doctorPatientDetail':
|
||||
return DoctorPatientDetailPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : DoctorPatientDetailPage(id: id);
|
||||
case 'doctorChat':
|
||||
return DoctorChatPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : DoctorChatPage(id: id);
|
||||
case 'doctorReportDetail':
|
||||
return DoctorReportDetailPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : DoctorReportDetailPage(id: id);
|
||||
case 'doctorFollowUpEdit':
|
||||
return DoctorFollowUpEditPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : DoctorFollowUpEditPage(id: id);
|
||||
case 'devices':
|
||||
return const DeviceManagementPage();
|
||||
case 'deviceScan':
|
||||
@@ -106,9 +123,11 @@ Widget buildPage(RouteInfo route, WidgetRef ref) {
|
||||
case 'conversationHistory':
|
||||
return const ConversationHistoryPage();
|
||||
case 'staticText':
|
||||
return StaticTextPage(type: params['type']!);
|
||||
final type = _requiredParam(params, 'type');
|
||||
return type == null ? _missingParamPage() : StaticTextPage(type: type);
|
||||
case 'dietDetail':
|
||||
return DietRecordDetailPage(id: params['id']!);
|
||||
final id = _requiredParam(params, 'id');
|
||||
return id == null ? _missingParamPage() : DietRecordDetailPage(id: id);
|
||||
default:
|
||||
return const LoginPage();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user