feat: 三端抽屉重构 + 配色系统更新 + 后台管理页精调 + 键盘抬起组件

## 抽屉重构
- admin_drawer / doctor_drawer / health_drawer 三端抽屉全部重做
- drawer_shell 增强

## 配色系统
- app_colors / app_module_visuals / app_theme 更新
- app.dart 启动流程调整

## 后台管理页
- admin_doctors_page 大幅重构(+251)
- admin_home / doctor_dashboard / doctor_reports / doctor_home / doctor_followups / doctor_settings 精调

## 患者端
- home_page / chat_messages_view / medication_list / notification_center / remaining_pages / exercise_plan / device / diet / consultation 微调

## 新增
- keyboard_lift.dart: 键盘抬起处理组件
- AGENTS.md: agent 指引文档

## 其他
- api_client IP 适配
- app_future_view 增强
- data_providers 调整
- secondary_page_visuals_test 更新
This commit is contained in:
MingNian
2026-07-19 19:11:30 +08:00
parent ae94ced2d5
commit 0d4fd88ce7
35 changed files with 1022 additions and 644 deletions

View File

@@ -97,141 +97,144 @@ class _AdminDoctorsPageState extends ConsumerState<AdminDoctorsPage> {
ref.listen(adminDoctorsRefreshSignalProvider, (previous, next) {
if (previous != null && previous != next) _load();
});
return Scaffold(
backgroundColor: AppColors.pageGrey,
floatingActionButton: FloatingActionButton(
backgroundColor: AppColors.primary,
onPressed: () => pushRoute(ref, 'adminAddDoctor'),
child: const Icon(Icons.add, color: Colors.white),
),
body: _loading
? const BackofficeLoadingState(message: '正在加载医生')
: _error != null
? BackofficeErrorState(message: _error!, onRetry: _load)
: _doctors.isEmpty
? const BackofficeEmptyState(
icon: Icons.medical_services_outlined,
title: '暂无医生',
description: '点击右下角按钮添加第一位医生',
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _doctors.length,
itemBuilder: (_, i) {
final d = _doctors[i];
final active = d['isActive'] != false;
return BackofficeSectionCard(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
child: Row(
children: [
CircleAvatar(
backgroundColor: active
? AppColors.successLight
: AppColors.background,
child: Icon(
Icons.local_hospital,
size: 20,
color: active
? AppColors.success
: AppColors.textHint,
),
final body = _loading
? const BackofficeLoadingState(message: '正在加载医生')
: _error != null
? BackofficeErrorState(message: _error!, onRetry: _load)
: _doctors.isEmpty
? const BackofficeEmptyState(
icon: Icons.medical_services_outlined,
title: '暂无医生',
description: '点击右下角按钮添加第一位医生',
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _doctors.length,
itemBuilder: (_, i) {
final d = _doctors[i];
final active = d['isActive'] != false;
return BackofficeSectionCard(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
child: Row(
children: [
CircleAvatar(
backgroundColor: active
? AppColors.successLight
: AppColors.background,
child: Icon(
Icons.local_hospital,
size: 20,
color: active ? AppColors.success : AppColors.textHint,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
d['name'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
if (!active)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'已停用',
style: TextStyle(
fontSize: 10,
color: AppColors.error,
),
),
),
],
),
const SizedBox(height: 4),
Text(
'${d['title'] ?? ''} · ${d['department'] ?? ''}',
style: const TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
),
),
if (d['professionalDirection'] != null)
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
d['professionalDirection']!,
d['name'] ?? '',
style: const TextStyle(
fontSize: 12,
color: AppColors.textHint,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
PopupMenuButton<String>(
onSelected: (v) {
if (v == 'edit') {
pushRoute(
ref,
'adminAddDoctor',
params: {
'id': d['id']?.toString() ?? '',
'phone': d['phone']?.toString() ?? '',
'name': d['name']?.toString() ?? '',
'title': d['title']?.toString() ?? '',
'department': d['department']?.toString() ?? '',
'direction':
d['professionalDirection']?.toString() ??
'',
},
);
}
if (v == 'toggle') _toggleActive(d['id']);
if (v == 'delete') _delete(d['id']);
},
itemBuilder: (_) => [
const PopupMenuItem(value: 'edit', child: Text('编辑')),
PopupMenuItem(
value: 'toggle',
child: Text(active ? '停用' : '启用'),
const SizedBox(width: 8),
if (!active)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.errorLight,
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'已停用',
style: TextStyle(
fontSize: 10,
color: AppColors.error,
),
),
),
],
),
const PopupMenuItem(
value: 'delete',
child: Text(
'删除',
style: TextStyle(color: AppColors.error),
const SizedBox(height: 4),
Text(
'${d['title'] ?? ''} · ${d['department'] ?? ''}',
style: const TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
),
),
if (d['professionalDirection'] != null)
Text(
d['professionalDirection']!,
style: const TextStyle(
fontSize: 12,
color: AppColors.textHint,
),
),
],
),
],
),
);
},
),
),
PopupMenuButton<String>(
onSelected: (v) {
if (v == 'edit') {
pushRoute(
ref,
'adminAddDoctor',
params: {
'id': d['id']?.toString() ?? '',
'phone': d['phone']?.toString() ?? '',
'name': d['name']?.toString() ?? '',
'title': d['title']?.toString() ?? '',
'department': d['department']?.toString() ?? '',
'direction':
d['professionalDirection']?.toString() ?? '',
},
);
}
if (v == 'toggle') _toggleActive(d['id']);
if (v == 'delete') _delete(d['id']);
},
itemBuilder: (_) => [
const PopupMenuItem(value: 'edit', child: Text('编辑')),
PopupMenuItem(
value: 'toggle',
child: Text(active ? '停用' : '启用'),
),
const PopupMenuItem(
value: 'delete',
child: Text(
'删除',
style: TextStyle(color: AppColors.error),
),
),
],
),
],
),
);
},
);
return Stack(
children: [
Positioned.fill(child: body),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
backgroundColor: AppColors.primary,
onPressed: () => pushRoute(ref, 'adminAddDoctor'),
child: const Icon(Icons.add, color: Colors.white),
),
),
],
);
}
}