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:
@@ -53,6 +53,7 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
setState(() => _data.removeAt(index));
|
||||
try {
|
||||
await ref.read(dietServiceProvider).deleteRecord(id);
|
||||
ref.invalidate(dietRecordsProvider);
|
||||
if (mounted) {
|
||||
AppToast.show(context, '已删除', type: AppToastType.success);
|
||||
}
|
||||
@@ -205,59 +206,66 @@ class _DietRecordListPageState extends ConsumerState<DietRecordListPage> {
|
||||
padding: EdgeInsets.only(right: i == dates.length - 1 ? 0 : 5),
|
||||
child: GestureDetector(
|
||||
onTap: () => setState(() => _selectedDate = d),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? AppColors.primary
|
||||
: (isToday ? DietPalette.primarySoft : Colors.white),
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
border: Border.all(
|
||||
child: CustomPaint(
|
||||
foregroundPainter: cal > 0
|
||||
? const _DietDateGradientBorderPainter()
|
||||
: null,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isSel
|
||||
? Colors.transparent
|
||||
: (isToday
|
||||
? const Color(0xFFC9D0FF)
|
||||
: AppColors.borderLight),
|
||||
? AppColors.primary
|
||||
: (isToday ? DietPalette.primarySoft : Colors.white),
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
border: cal > 0
|
||||
? null
|
||||
: Border.all(
|
||||
color: isSel
|
||||
? Colors.transparent
|
||||
: (isToday
|
||||
? const Color(0xFFC9D0FF)
|
||||
: AppColors.borderLight),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
isToday
|
||||
? '今天'
|
||||
: ['一', '二', '三', '四', '五', '六', '日'][d.weekday -
|
||||
1],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isSel ? Colors.white : AppColors.textHint,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
isToday
|
||||
? '今天'
|
||||
: ['一', '二', '三', '四', '五', '六', '日'][d.weekday -
|
||||
1],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isSel ? Colors.white : AppColors.textHint,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
'${d.day}',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isSel ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (cal > 0) ...[
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
'$cal',
|
||||
'${d.day}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSel ? Colors.white70 : AppColors.textHint,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isSel ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (cal > 0) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'$cal',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSel ? Colors.white70 : AppColors.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -557,7 +565,7 @@ class _DietTrendPanel extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
@@ -633,14 +641,15 @@ class _TrendChart extends StatelessWidget {
|
||||
'${data[i].toInt()}',
|
||||
style: const TextStyle(
|
||||
fontSize: 8,
|
||||
color: AppColors.textHint,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
height: h,
|
||||
decoration: BoxDecoration(
|
||||
color: data[i] > 0 ? DietPalette.primary : AppColors.border,
|
||||
color: data[i] > 0 ? null : AppColors.border,
|
||||
gradient: data[i] > 0 ? AppColors.primaryGradient : null,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
@@ -653,6 +662,32 @@ class _TrendChart extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _DietDateGradientBorderPainter extends CustomPainter {
|
||||
const _DietDateGradientBorderPainter();
|
||||
|
||||
static const _strokeWidth = 1.5;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final rect = (Offset.zero & size).deflate(_strokeWidth / 2);
|
||||
final paint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = _strokeWidth
|
||||
..shader = AppColors.primaryGradient.createShader(rect)
|
||||
..isAntiAlias = true;
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
rect,
|
||||
const Radius.circular(AppTheme.rMd - _strokeWidth / 2),
|
||||
),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_DietDateGradientBorderPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
class _SwipeAction extends StatelessWidget {
|
||||
final Key itemKey;
|
||||
final Widget child;
|
||||
@@ -709,19 +744,6 @@ class DietRecordDetailPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _DietRecordDetailPageState extends ConsumerState<DietRecordDetailPage> {
|
||||
late Future<List<Map<String, dynamic>>> _recordsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_recordsFuture = _loadRecords();
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> _loadRecords() =>
|
||||
ref.read(dietServiceProvider).getRecords();
|
||||
|
||||
void _reload() => setState(() => _recordsFuture = _loadRecords());
|
||||
|
||||
String _formatRecordedAt(Object? value) {
|
||||
final date = DateTime.tryParse(value?.toString() ?? '')?.toLocal();
|
||||
if (date == null) return '';
|
||||
@@ -742,22 +764,12 @@ class _DietRecordDetailPageState extends ConsumerState<DietRecordDetailPage> {
|
||||
),
|
||||
title: const Text('饮食详情'),
|
||||
),
|
||||
body: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _recordsFuture,
|
||||
builder: (ctx, snap) {
|
||||
if (!snap.hasData) {
|
||||
if (snap.hasError) {
|
||||
return AppErrorState(
|
||||
title: '饮食记录加载失败',
|
||||
subtitle: '请检查网络后重新进入',
|
||||
onRetry: _reload,
|
||||
);
|
||||
}
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: AppTheme.primary),
|
||||
);
|
||||
}
|
||||
final d = snap.data!.firstWhere(
|
||||
body: AppAsyncValueView<List<Map<String, dynamic>>>(
|
||||
value: ref.watch(dietRecordsProvider),
|
||||
errorTitle: '饮食记录加载失败',
|
||||
onRetry: () => ref.invalidate(dietRecordsProvider),
|
||||
onData: (ctx, records) {
|
||||
final d = records.firstWhere(
|
||||
(r) => r['id']?.toString() == widget.id,
|
||||
orElse: () => <String, dynamic>{},
|
||||
);
|
||||
@@ -2370,18 +2382,6 @@ class FollowUpListPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _FollowUpListPageState extends ConsumerState<FollowUpListPage> {
|
||||
Future<List<Map<String, dynamic>>>? _future;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
}
|
||||
|
||||
void _load() => setState(() {
|
||||
_future = ref.read(followUpServiceProvider).getList();
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GradientScaffold(
|
||||
@@ -2393,18 +2393,11 @@ class _FollowUpListPageState extends ConsumerState<FollowUpListPage> {
|
||||
title: const Text('复查随访'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _future,
|
||||
builder: (ctx, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: AppTheme.primaryLight),
|
||||
);
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return AppErrorState(title: '随访加载失败', onRetry: _load);
|
||||
}
|
||||
final list = snap.data ?? [];
|
||||
body: AppAsyncValueView<List<Map<String, dynamic>>>(
|
||||
value: ref.watch(followUpListProvider),
|
||||
errorTitle: '随访加载失败',
|
||||
onRetry: () => ref.invalidate(followUpListProvider),
|
||||
onData: (ctx, list) {
|
||||
if (list.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user