feat: 医生端完整实现 + 双角色登录 + 健康日历 + 饮食记录重做 + 配色系统重构
后端: - User表加Role字段 + DoctorProfile表 + 废弃旧Doctor数据 - JWT加Role claim + 注册/登录拆分(/api/auth/register + /api/auth/login) - 医生端点全量加JWT鉴权+Role校验(15个端点) - 日历端点改造(用药/运动/随访按日期+星期匹配) - 饮食记录PUT端点 + 健康记录DELETE端点 - 用户Profile端点返回Role 前端: - 登录页重做(角色选择卡片 用户紫/医生蓝 + 审核码6666 + 注册返登录) - App路由分流(userRole→患者端/医生端) - 医生端: 工作台Dashboard + 患者管理(搜索分页) + 问诊/报告/随访CRUD - 报告审核: AI预分析+指标表格+严重程度4级+模板多选+评语 - 随访编辑: 患者选择器+日期时间+打通患者端 - 健康日历: 可点击日历+当日安排列表+用药/运动/随访颜色标记 - 饮食记录: 热量趋势7/30天+日历(一周)+当日胶囊详情+侧滑编辑删除 - 健康档案: 日期选择器+手术可添加多条+白底渐变保存按钮 - 配色系统: 清理25个未使用色+新增doctorBlue/drawerGradient/pageGrey - 患者端侧边栏: 新渐变+白底按钮+紫色汉堡图标 - AI对话: 用户气泡白底黑字+药管家改蓝色 - App启动闪屏: 防登录页闪烁 文档: color_design_system.md(20页面完整配色设计+图标底色对照表)
This commit is contained in:
105
health_app/lib/widgets/doctor_drawer.dart
Normal file
105
health_app/lib/widgets/doctor_drawer.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../core/app_colors.dart';
|
||||
import '../core/navigation_provider.dart';
|
||||
import '../providers/auth_provider.dart';
|
||||
import '../pages/doctor/doctor_home_page.dart' show doctorPageProvider;
|
||||
|
||||
class DoctorDrawer extends ConsumerWidget {
|
||||
const DoctorDrawer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final auth = ref.watch(authProvider);
|
||||
final currentPage = ref.watch(doctorPageProvider);
|
||||
|
||||
return Drawer(
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: SafeArea(
|
||||
child: Column(children: [
|
||||
// 医生信息头部
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: AppColors.doctorGradient,
|
||||
),
|
||||
child: Column(children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
pushRoute(ref, 'doctorProfile');
|
||||
},
|
||||
child: CircleAvatar(
|
||||
radius: 30,
|
||||
backgroundColor: Colors.white24,
|
||||
child: Text(
|
||||
(auth.user?.name ?? '医生')[0],
|
||||
style: const TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(auth.user?.name ?? '未设置姓名', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const Text('点击完善信息', style: TextStyle(color: Colors.white70, fontSize: 12)),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_DrawerItem(icon: Icons.dashboard_outlined, label: '工作台', selected: currentPage == 'dashboard', onTap: () { Navigator.pop(context); ref.read(doctorPageProvider.notifier).set('dashboard'); }),
|
||||
_DrawerItem(icon: Icons.people_outline, label: '患者管理', selected: currentPage == 'patients', onTap: () { Navigator.pop(context); ref.read(doctorPageProvider.notifier).set('patients'); }),
|
||||
_DrawerItem(icon: Icons.chat_outlined, label: '问诊列表', selected: currentPage == 'consultations', onTap: () { Navigator.pop(context); ref.read(doctorPageProvider.notifier).set('consultations'); }),
|
||||
_DrawerItem(icon: Icons.description_outlined, label: '报告审核', selected: currentPage == 'reports', onTap: () { Navigator.pop(context); ref.read(doctorPageProvider.notifier).set('reports'); }),
|
||||
_DrawerItem(icon: Icons.event_note_outlined, label: '复查随访', selected: currentPage == 'followups', onTap: () { Navigator.pop(context); ref.read(doctorPageProvider.notifier).set('followups'); }),
|
||||
const Spacer(),
|
||||
const Divider(),
|
||||
_DrawerItem(icon: Icons.settings_outlined, label: '设置', selected: false, onTap: () { Navigator.pop(context); pushRoute(ref, 'doctorSettings'); }),
|
||||
_DrawerItem(icon: Icons.logout, label: '退出登录', selected: false, onTap: () async {
|
||||
Navigator.pop(context);
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
goRoute(ref, 'login');
|
||||
}),
|
||||
const SizedBox(height: 16),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DrawerItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _DrawerItem({required this.icon, required this.label, required this.selected, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
||||
child: Material(
|
||||
color: selected ? AppColors.doctorBlue.withOpacity(0.08) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: selected ? AppColors.doctorBlue.withOpacity(0.15) : const Color(0xFFF0F0F0), width: 1),
|
||||
),
|
||||
child: Row(children: [
|
||||
Icon(icon, size: 20, color: selected ? AppColors.doctorBlue : AppColors.textHint),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: Text(label, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, color: selected ? AppColors.doctorBlue : AppColors.textPrimary))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class HealthDrawer extends ConsumerWidget {
|
||||
return Drawer(
|
||||
width: MediaQuery.of(context).size.width * 0.85,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
|
||||
decoration: const BoxDecoration(gradient: AppColors.drawerGradient),
|
||||
child: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 0),
|
||||
@@ -141,9 +141,9 @@ class HealthDrawer extends ConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(AppTheme.rSm),
|
||||
),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
const Icon(Icons.settings_outlined, size: 22, color: AppColors.textPrimary),
|
||||
const Icon(Icons.settings_outlined, size: 22, color: AppColors.primary),
|
||||
const SizedBox(height: 2),
|
||||
const Text('设置', style: TextStyle(fontSize: 11, color: AppColors.textPrimary)),
|
||||
const Text('设置', style: TextStyle(fontSize: 11, color: AppColors.primary)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -167,6 +167,7 @@ class HealthDrawer extends ConsumerWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
border: Border.all(color: Color(0xFFC8DDFD), width: 1.5),
|
||||
),
|
||||
|
||||
@@ -200,34 +200,30 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
));
|
||||
}
|
||||
|
||||
Widget card = Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(18, 16, 18, 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题行 + 详情入口
|
||||
Row(children: [
|
||||
Text(package.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.text)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('详情', style: TextStyle(fontSize: 16, color: AppTheme.textSub)),
|
||||
const SizedBox(width: 2),
|
||||
Icon(Icons.chevron_right, size: 21, color: AppTheme.textHint),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
// 服务图标网格
|
||||
...serviceRows,
|
||||
],
|
||||
),
|
||||
Widget card = GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(18, 16, 18, 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题行 + 详情入口
|
||||
Row(children: [
|
||||
Text(package.title, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: AppTheme.text)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('详情', style: TextStyle(fontSize: 16, color: AppTheme.textSub)),
|
||||
const SizedBox(width: 2),
|
||||
Icon(Icons.chevron_right, size: 21, color: AppTheme.textHint),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
// 服务图标网格
|
||||
...serviceRows,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user