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:
@@ -121,6 +121,7 @@ class _AdminAddDoctorPageState extends ConsumerState<AdminAddDoctorPage> {
|
||||
),
|
||||
body: BackofficeSurface(
|
||||
child: SingleChildScrollView(
|
||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,31 +16,48 @@ class AdminPageNotifier extends Notifier<String> {
|
||||
void set(String page) => state = page;
|
||||
}
|
||||
|
||||
class AdminHomePage extends ConsumerWidget {
|
||||
class AdminHomePage extends ConsumerStatefulWidget {
|
||||
const AdminHomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final page = ref.watch(adminPageProvider);
|
||||
ConsumerState<AdminHomePage> createState() => _AdminHomePageState();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.pageGrey,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
page == 'patients' ? '患者管理' : '医生管理',
|
||||
style: TextStyle(color: AppColors.textPrimary),
|
||||
class _AdminHomePageState extends ConsumerState<AdminHomePage> {
|
||||
final List<Widget?> _pages = [const AdminDoctorsPage(), null];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final page = ref.watch(adminPageProvider);
|
||||
final pageIndex = page == 'patients' ? 1 : 0;
|
||||
_pages[pageIndex] ??= const AdminPatientsPage();
|
||||
|
||||
return PopScope(
|
||||
canPop: page == 'doctors',
|
||||
onPopInvokedWithResult: (didPop, _) {
|
||||
if (!didPop) ref.read(adminPageProvider.notifier).set('doctors');
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.pageGrey,
|
||||
drawerEnableOpenDragGesture: true,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
page == 'patients' ? '患者管理' : '医生管理',
|
||||
style: TextStyle(color: AppColors.textPrimary),
|
||||
),
|
||||
iconTheme: const IconThemeData(color: AppColors.textPrimary),
|
||||
),
|
||||
drawer: const AdminDrawer(),
|
||||
body: BackofficeSurface(
|
||||
child: IndexedStack(
|
||||
index: pageIndex,
|
||||
children: _pages
|
||||
.map((page) => page ?? const SizedBox.shrink())
|
||||
.toList(growable: false),
|
||||
),
|
||||
),
|
||||
iconTheme: const IconThemeData(color: AppColors.textPrimary),
|
||||
),
|
||||
drawer: const AdminDrawer(),
|
||||
body: BackofficeSurface(
|
||||
child: switch (page) {
|
||||
'doctors' => const AdminDoctorsPage(),
|
||||
'patients' => const AdminPatientsPage(),
|
||||
_ => const AdminDoctorsPage(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user