fix: 健康仪表盘单位下移 + 档案按钮浅紫 + AI问诊胶囊改名 + 删除账号功能
This commit is contained in:
@@ -302,8 +302,14 @@ class DietNotifier extends Notifier<DietState> {
|
||||
}
|
||||
|
||||
// ─────────── 饮食主题色(暖橙系,不再用紫色)───────────
|
||||
const _dietAccent = AppColors.primary;
|
||||
const _dietAccentLight = AppColors.primarySoft;
|
||||
const _dietAccent = Color(0xFFF97316);
|
||||
const _dietAccentLight = Color(0xFFFFF3E0);
|
||||
const _dietKcalText = Color(0xFF9A3412);
|
||||
const _dietGradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFF6D365), Color(0xFFFDA085)],
|
||||
);
|
||||
|
||||
class DietCapturePage extends ConsumerStatefulWidget {
|
||||
const DietCapturePage({super.key});
|
||||
@@ -404,11 +410,11 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
('🍪', '加餐', 'snack'),
|
||||
];
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.surfaceGradient,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
color: Colors.white.withValues(alpha: 0.92),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white, width: 1.4),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Row(
|
||||
@@ -419,23 +425,28 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
onTap: () => ref.read(dietProvider.notifier).setMealType(m.$3),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
curve: Curves.easeOutCubic,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.symmetric(vertical: 11),
|
||||
decoration: BoxDecoration(
|
||||
gradient: sel ? AppColors.warmCareGradient : null,
|
||||
color: sel ? null : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: sel ? _dietGradient : null,
|
||||
color: sel ? null : const Color(0xFFFFFBF6),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: sel ? Colors.transparent : const Color(0xFFFFE4CA),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(m.$1, style: const TextStyle(fontSize: 22)),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox.shrink(),
|
||||
const SizedBox(height: 0),
|
||||
Text(
|
||||
m.$2,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: sel ? Colors.white : AppColors.textHint,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: sel ? Colors.white : _dietKcalText,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -523,7 +534,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.warningLight,
|
||||
color: const Color(0xFFFFF3D8),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
@@ -531,7 +542,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.warning,
|
||||
color: _dietKcalText,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -645,7 +656,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.warning,
|
||||
color: _dietKcalText,
|
||||
),
|
||||
align: TextAlign.right,
|
||||
keyboardType: TextInputType.number,
|
||||
@@ -656,7 +667,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
'kcal',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.textHint,
|
||||
color: Color(0xFFB45309),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -715,7 +726,7 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: AppColors.primary),
|
||||
borderSide: const BorderSide(color: _dietAccent),
|
||||
),
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(fontSize: 14, color: AppColors.textHint),
|
||||
@@ -737,62 +748,62 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: CircularProgressIndicator(
|
||||
value: (totalCal / 700).clamp(0.0, 1.0),
|
||||
strokeWidth: 4,
|
||||
backgroundColor: AppColors.borderLight,
|
||||
color: _dietAccent,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
const Text(
|
||||
'本餐热量',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'$totalCal',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
SizedBox(
|
||||
width: 56,
|
||||
height: 56,
|
||||
child: CircularProgressIndicator(
|
||||
value: (totalCal / 700).clamp(0.0, 1.0),
|
||||
strokeWidth: 4,
|
||||
backgroundColor: AppColors.borderLight,
|
||||
color: _dietAccent,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'kcal',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'$totalCal',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: _dietAccent,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
'kcal',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'本餐热量',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
_macro('碳水', 0.55, const Color(0xFFF5A623)),
|
||||
const SizedBox(width: 8),
|
||||
@@ -801,8 +812,8 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
_macro('脂肪', 0.20, const Color(0xFFE8686A)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -855,28 +866,46 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: _dietAccentLight,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
const Text(
|
||||
'AI建议',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
child: const Icon(Icons.auto_awesome, size: 20, color: _dietAccent),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.6,
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: _dietAccentLight,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.auto_awesome,
|
||||
size: 20,
|
||||
color: _dietAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -889,9 +918,15 @@ class _DietCapturePageState extends ConsumerState<DietCapturePage> {
|
||||
width: double.infinity,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.warmCareGradient,
|
||||
gradient: _dietGradient,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: AppColors.buttonShadow,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _dietAccent.withValues(alpha: 0.24),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
|
||||
@@ -176,7 +176,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
}
|
||||
|
||||
static final _agentDefs = [
|
||||
(ActiveAgent.consultation, '问诊', LucideIcons.messageCircle),
|
||||
(ActiveAgent.consultation, 'AI问诊', LucideIcons.messageCircle),
|
||||
(ActiveAgent.health, '记数据', LucideIcons.heartPulse),
|
||||
(ActiveAgent.diet, '拍饮食', LucideIcons.utensils),
|
||||
(ActiveAgent.medication, '药管家', LucideIcons.pill),
|
||||
@@ -244,12 +244,36 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
|
||||
LinearGradient _agentGradient(ActiveAgent agent) {
|
||||
return switch (agent) {
|
||||
ActiveAgent.consultation => AppColors.sageVioletGradient,
|
||||
ActiveAgent.health => AppColors.meadowVioletGradient,
|
||||
ActiveAgent.diet => AppColors.warmCareGradient,
|
||||
ActiveAgent.medication => AppColors.primaryGradient,
|
||||
ActiveAgent.report => AppColors.roseVioletGradient,
|
||||
ActiveAgent.exercise => AppColors.calmHealthGradient,
|
||||
ActiveAgent.consultation => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFFCAD4), Color(0xFFFF9AAE)],
|
||||
),
|
||||
ActiveAgent.health => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFFAFBD), Color(0xFFC9FFBF)],
|
||||
),
|
||||
ActiveAgent.diet => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFF6D365), Color(0xFFFDA085)],
|
||||
),
|
||||
ActiveAgent.medication => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF89F7FE), Color(0xFF66A6FF)],
|
||||
),
|
||||
ActiveAgent.report => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFE0C3FC), Color(0xFF8EC5FC)],
|
||||
),
|
||||
ActiveAgent.exercise => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFB6F2FF), Color(0xFF7DD3FC)],
|
||||
),
|
||||
_ => AppColors.primaryGradient,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../../../core/app_colors.dart';
|
||||
import '../../../core/app_theme.dart';
|
||||
import '../../../core/navigation_provider.dart';
|
||||
@@ -294,12 +295,27 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
|
||||
// ── 医生选择区(问诊专用)──
|
||||
// ── AI问诊引导 ──
|
||||
if (agent == ActiveAgent.consultation) ...[
|
||||
const SizedBox(height: 14),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: _buildDoctorCards(ref, agentColors),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0F4FF),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: const Text(
|
||||
'在下方输入框描述您的症状,AI医生将为您提供专业建议',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -409,13 +425,18 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ShaderMask(
|
||||
shaderCallback: (bounds) => AppColors.actionOutlineGradient.createShader(bounds),
|
||||
shaderCallback: (bounds) =>
|
||||
AppColors.actionOutlineGradient.createShader(bounds),
|
||||
child: Icon(a.icon, size: 24, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
a.label,
|
||||
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -436,113 +457,6 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
};
|
||||
}
|
||||
|
||||
Widget _buildDoctorCards(WidgetRef ref, _AgentColors colors) {
|
||||
const doctors = [
|
||||
{
|
||||
'name': '张明',
|
||||
'title': '主任医师',
|
||||
'dept': '心脏康复科',
|
||||
'desc': '冠心病、高血压术后管理',
|
||||
'id': '468b82e2-d95a-4436-bff6-a50eecf99a66',
|
||||
},
|
||||
{
|
||||
'name': '李芳',
|
||||
'title': '副主任医师',
|
||||
'dept': '营养科',
|
||||
'desc': '糖尿病、甲状腺疾病管理',
|
||||
'id': 'd4148733-b538-4398-af17-0c7592fc0c2d',
|
||||
},
|
||||
{
|
||||
'name': '王建国',
|
||||
'title': '主任医师',
|
||||
'dept': '心血管内科',
|
||||
'desc': '术后营养指导、饮食方案制定',
|
||||
'id': 'ef0953c9-eb63-4d03-b6d7-050a1897d4a3',
|
||||
},
|
||||
];
|
||||
return Row(
|
||||
children: [
|
||||
for (var i = 0; i < doctors.length; i++) ...[
|
||||
if (i > 0) const SizedBox(width: 8),
|
||||
Expanded(child: _doctorCard(doctors[i], ref, colors)),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _doctorCard(
|
||||
Map<String, String> doc,
|
||||
WidgetRef ref,
|
||||
_AgentColors colors,
|
||||
) {
|
||||
return InkWell(
|
||||
onTap: () => pushRoute(ref, 'consultation', params: {'id': doc['id']!}),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: colors.border),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: colors.iconBg,
|
||||
child: Text(
|
||||
doc['name']![0],
|
||||
style: TextStyle(
|
||||
fontSize: 21,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colors.accent,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
doc['name']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
doc['title']!,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.textHint),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.iconBg,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
doc['dept']!,
|
||||
style: TextStyle(fontSize: 13, color: colors.accent),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
doc['desc']!,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.3,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 2. DataConfirmCard — 统一确认卡片(紫白蓝风格)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -1556,46 +1470,52 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
static _AgentColors _agentColors(ActiveAgent agent) {
|
||||
return switch (agent) {
|
||||
ActiveAgent.consultation => _AgentColors(
|
||||
gradient: [AppColors.sageAccent, AppColors.primary],
|
||||
bg: const Color(0xFFF2FBF9),
|
||||
border: const Color(0xFFD9F0EA),
|
||||
iconBg: const Color(0xFFE8F8F4),
|
||||
accent: const Color(0xFF4F9E90),
|
||||
gradient: [Color(0xFFFFCAD4), Color(0xFFFF9AAE)],
|
||||
bg: const Color(0xFFFFF4F6),
|
||||
border: const Color(0xFFFFDFE7),
|
||||
iconBg: const Color(0xFFFFEEF2),
|
||||
accent: const Color(0xFFFF7F98),
|
||||
verticalGradient: true,
|
||||
),
|
||||
ActiveAgent.health => _AgentColors(
|
||||
gradient: [AppColors.meadowAccent, AppColors.auraDeepIndigo],
|
||||
bg: const Color(0xFFF3FBF4),
|
||||
border: const Color(0xFFDFF2E4),
|
||||
iconBg: const Color(0xFFEFF9F0),
|
||||
accent: const Color(0xFF4EA65B),
|
||||
gradient: [Color(0xFFFFAFBD), Color(0xFFC9FFBF)],
|
||||
bg: const Color(0xFFF7FDEB),
|
||||
border: const Color(0xFFEAF7BF),
|
||||
iconBg: const Color(0xFFF9FCEB),
|
||||
accent: const Color(0xFF8BD982),
|
||||
verticalGradient: true,
|
||||
),
|
||||
ActiveAgent.diet => _AgentColors(
|
||||
gradient: [AppColors.peachAccent, AppColors.auraOrchid],
|
||||
gradient: [Color(0xFFF6D365), Color(0xFFFDA085)],
|
||||
bg: const Color(0xFFFFF7F0),
|
||||
border: const Color(0xFFFFE8D4),
|
||||
iconBg: const Color(0xFFFFF0E4),
|
||||
accent: const Color(0xFFE26A64),
|
||||
accent: const Color(0xFFF89C5B),
|
||||
verticalGradient: true,
|
||||
),
|
||||
ActiveAgent.medication => _AgentColors(
|
||||
gradient: [AppColors.auraLavender, AppColors.blueMeasure],
|
||||
gradient: [Color(0xFF89F7FE), Color(0xFF66A6FF)],
|
||||
bg: const Color(0xFFF4F6FF),
|
||||
border: const Color(0xFFDDE6FF),
|
||||
iconBg: const Color(0xFFEFF3FF),
|
||||
accent: AppColors.primary,
|
||||
accent: const Color(0xFF4F8FF7),
|
||||
verticalGradient: true,
|
||||
),
|
||||
ActiveAgent.report => _AgentColors(
|
||||
gradient: [AppColors.primary, AppColors.roseAccent],
|
||||
gradient: [Color(0xFFE0C3FC), Color(0xFF8EC5FC)],
|
||||
bg: const Color(0xFFF8F5FF),
|
||||
border: const Color(0xFFE9E0FF),
|
||||
iconBg: const Color(0xFFF2EDFF),
|
||||
accent: AppColors.primary,
|
||||
accent: const Color(0xFF9D8AF8),
|
||||
verticalGradient: true,
|
||||
),
|
||||
ActiveAgent.exercise => _AgentColors(
|
||||
gradient: [AppColors.sageAccent, AppColors.auraDeepIndigo],
|
||||
bg: const Color(0xFFF0FBFD),
|
||||
border: const Color(0xFFD6EEF2),
|
||||
iconBg: const Color(0xFFE8F7FA),
|
||||
accent: const Color(0xFF2B8EA0),
|
||||
gradient: [Color(0xFFB6F2FF), Color(0xFF7DD3FC)],
|
||||
bg: const Color(0xFFF0FCFF),
|
||||
border: const Color(0xFFD8F5FC),
|
||||
iconBg: const Color(0xFFEAFBFF),
|
||||
accent: const Color(0xFF38BDE8),
|
||||
verticalGradient: true,
|
||||
),
|
||||
_ => _AgentColors(
|
||||
gradient: [AppColors.primary, AppColors.blueMeasure],
|
||||
@@ -1609,16 +1529,16 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
|
||||
static (_AgentIcon, String, String) _agentInfo(ActiveAgent agent) {
|
||||
return switch (agent) {
|
||||
ActiveAgent.health => (Icons.favorite_border, '记数据', '录入血压、血糖、心率等日常指标'),
|
||||
ActiveAgent.diet => (Icons.restaurant, '拍饮食', '拍照识别食物热量和营养成分'),
|
||||
ActiveAgent.medication => (Icons.medication, '药管家', '管理药品、提醒服药、追踪用量'),
|
||||
ActiveAgent.health => (LucideIcons.heartPulse, '记数据', '录入血压、血糖、心率等日常指标'),
|
||||
ActiveAgent.diet => (LucideIcons.utensils, '拍饮食', '拍照识别食物热量和营养成分'),
|
||||
ActiveAgent.medication => (LucideIcons.pill, '药管家', '管理药品、提醒服药、追踪用量'),
|
||||
ActiveAgent.consultation => (
|
||||
Icons.local_hospital,
|
||||
'问诊',
|
||||
'在线咨询医生,描述症状获取建议',
|
||||
LucideIcons.messageCircle,
|
||||
'AI问诊',
|
||||
'AI智能问诊,描述症状获取建议',
|
||||
),
|
||||
ActiveAgent.report => (Icons.assignment, '报告分析', '上传体检报告,AI 辅助解读'),
|
||||
ActiveAgent.exercise => (Icons.directions_run, '运动', '制定运动计划,打卡记录进度'),
|
||||
ActiveAgent.report => (LucideIcons.fileText, '报告分析', '上传体检报告,AI 辅助解读'),
|
||||
ActiveAgent.exercise => (LucideIcons.activity, '运动', '制定运动计划,打卡记录进度'),
|
||||
_ => (Icons.forum_outlined, 'AI 助手', '您的智能健康管家'),
|
||||
};
|
||||
}
|
||||
@@ -2031,13 +1951,17 @@ class _AgentMark extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: colors.gradient,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
begin: colors.verticalGradient
|
||||
? Alignment.topCenter
|
||||
: Alignment.topLeft,
|
||||
end: colors.verticalGradient
|
||||
? Alignment.bottomCenter
|
||||
: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(colors.verticalGradient ? 18 : 20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colors.accent.withValues(alpha: 0.24),
|
||||
color: colors.accent.withValues(alpha: 0.18),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
@@ -2103,12 +2027,14 @@ class _AgentColors {
|
||||
final Color border;
|
||||
final Color iconBg;
|
||||
final Color accent;
|
||||
final bool verticalGradient;
|
||||
const _AgentColors({
|
||||
required this.gradient,
|
||||
required this.bg,
|
||||
required this.border,
|
||||
required this.iconBg,
|
||||
required this.accent,
|
||||
this.verticalGradient = false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,127 +17,85 @@ class ProfilePage extends ConsumerWidget {
|
||||
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.9),
|
||||
title: const Text('个人信息'),
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.86),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 19),
|
||||
onPressed: () => popRoute(ref),
|
||||
),
|
||||
title: const Text(
|
||||
'个人信息',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w800),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 34),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 头像区
|
||||
Center(
|
||||
child: CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: AppColors.primaryLight,
|
||||
backgroundImage: user?.avatarUrl != null
|
||||
? NetworkImage(user!.avatarUrl!)
|
||||
: null,
|
||||
child: user?.avatarUrl == null
|
||||
? const Icon(
|
||||
Icons.person,
|
||||
size: 48,
|
||||
color: AppColors.textHint,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
_ProfileHero(
|
||||
name: name,
|
||||
phone: phone,
|
||||
avatarUrl: user?.avatarUrl,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
_InfoPanel(
|
||||
children: [
|
||||
_InfoRow(
|
||||
icon: Icons.badge_rounded,
|
||||
label: '姓名',
|
||||
value: name,
|
||||
colors: const [Color(0xFF7DD3FC), Color(0xFF2563EB)],
|
||||
),
|
||||
const _SoftDivider(),
|
||||
_InfoRow(
|
||||
icon: Icons.phone_iphone_rounded,
|
||||
label: '手机号',
|
||||
value: phone.isNotEmpty ? phone : '未绑定手机',
|
||||
colors: const [Color(0xFFA78BFA), Color(0xFF7C3AED)],
|
||||
),
|
||||
const _SoftDivider(),
|
||||
_InfoRow(
|
||||
icon: Icons.folder_shared_rounded,
|
||||
label: '健康档案',
|
||||
value: '查看和维护基础健康资料',
|
||||
colors: const [Color(0xFF2DD4BF), Color(0xFF0F766E)],
|
||||
onTap: () => pushRoute(ref, 'healthArchive'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.surfaceGradient,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.phone_android,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
phone.isNotEmpty ? phone : '未绑定手机',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
_InfoPanel(
|
||||
children: [
|
||||
_InfoRow(
|
||||
icon: Icons.verified_user_rounded,
|
||||
label: '隐私保护',
|
||||
value: '健康数据仅用于个人健康管理',
|
||||
colors: const [Color(0xFFFBBF24), Color(0xFFEA580C)],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 基础信息卡片
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.surfaceGradient,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rLg),
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_infoRow(Icons.person_outline, '姓名', name),
|
||||
const Divider(height: 24, color: AppColors.borderLight),
|
||||
_infoRow(
|
||||
Icons.phone_android,
|
||||
'手机号',
|
||||
phone.isNotEmpty ? phone : '未绑定',
|
||||
),
|
||||
const Divider(height: 24, color: AppColors.borderLight),
|
||||
_infoRow(
|
||||
Icons.health_and_safety,
|
||||
'健康档案',
|
||||
'查看详情',
|
||||
onTap: () => pushRoute(ref, 'healthArchive'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// 退出登录
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _logout(context, ref),
|
||||
icon: const Icon(Icons.logout, size: 18),
|
||||
label: const Text('退出登录', style: TextStyle(fontSize: 16)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.error,
|
||||
side: const BorderSide(color: AppColors.error),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
const SizedBox(height: 28),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _logout(context, ref),
|
||||
icon: const Icon(Icons.logout_rounded, size: 19),
|
||||
label: const Text('退出登录'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.error,
|
||||
side: BorderSide(
|
||||
color: AppColors.error.withValues(alpha: 0.34),
|
||||
),
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -145,61 +103,6 @@ class ProfilePage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(
|
||||
IconData icon,
|
||||
String label,
|
||||
String value, {
|
||||
VoidCallback? onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: AppColors.primaryGradient,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Icon(icon, size: 22, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (onTap != null)
|
||||
const Icon(
|
||||
Icons.chevron_right,
|
||||
size: 20,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _logout(BuildContext context, WidgetRef ref) async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
@@ -227,3 +130,200 @@ class ProfilePage extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileHero extends StatelessWidget {
|
||||
final String name;
|
||||
final String phone;
|
||||
final String? avatarUrl;
|
||||
const _ProfileHero({
|
||||
required this.name,
|
||||
required this.phone,
|
||||
required this.avatarUrl,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFEEF2FF), Color(0xFFF7ECFF), Color(0xFFEFFBF9)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF6D5DF6).withValues(alpha: 0.10),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 76,
|
||||
height: 76,
|
||||
padding: const EdgeInsets.all(3),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF38BDF8), Color(0xFFC084FC)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
child: avatarUrl != null
|
||||
? Image.network(avatarUrl!, fit: BoxFit.cover)
|
||||
: const ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Icon(
|
||||
Icons.person_rounded,
|
||||
color: Color(0xFF7C3AED),
|
||||
size: 42,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Text(
|
||||
phone.isNotEmpty ? phone : '未绑定手机',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoPanel extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
const _InfoPanel({required this.children});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.88),
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
border: Border.all(color: Colors.white, width: 1.4),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Column(children: children),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoRow extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
final List<Color> colors;
|
||||
final VoidCallback? onTap;
|
||||
const _InfoRow({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.colors,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: colors,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 22),
|
||||
),
|
||||
const SizedBox(width: 13),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (onTap != null)
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 16,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SoftDivider extends StatelessWidget {
|
||||
const _SoftDivider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Divider(height: 1, color: Color(0xFFF1F3F8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../widgets/service_package_card.dart';
|
||||
|
||||
class ServicePackageDetailPage extends ConsumerWidget {
|
||||
final String packageId;
|
||||
const ServicePackageDetailPage({super.key, required this.packageId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final package = servicePackages.where((p) => p.id == packageId).firstOrNull;
|
||||
if (package == null) {
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(title: const Text('服务包详情')),
|
||||
body: const Center(child: Text('未找到该服务包')),
|
||||
);
|
||||
}
|
||||
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(package.title, style: const TextStyle(fontSize: 19)),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 头部卡片
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
package.headerColor,
|
||||
package.headerColor.withAlpha(180),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: AppColors.cardShadow,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(40),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Text(
|
||||
'VIP 产品权益',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
package.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 25,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
package.subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
color: Colors.white.withAlpha(200),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 服务图标
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: package.services
|
||||
.map(
|
||||
(s) => SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 72) / 4,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.primaryLight,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(
|
||||
s.icon,
|
||||
size: 25,
|
||||
color: AppTheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
s.label,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
color: AppTheme.textSub,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
// 适用人群
|
||||
_Section(
|
||||
title: '适用人群',
|
||||
child: Text(
|
||||
package.targetAudience,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
color: AppTheme.textSub,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 详细说明
|
||||
...package.detailSections.map(
|
||||
(s) => _Section(
|
||||
title: s.title,
|
||||
child: Text(
|
||||
s.content,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
color: AppTheme.textSub,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Section extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget child;
|
||||
const _Section({required this.title, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppTheme.text,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
child,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,60 +5,364 @@ import '../../core/app_colors.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../widgets/app_menu_item.dart';
|
||||
import '../../providers/data_providers.dart' show apiClientProvider;
|
||||
|
||||
class SettingsPage extends ConsumerWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override Widget build(BuildContext context, WidgetRef ref) {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return GradientScaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColors.cardBackground,
|
||||
leading: IconButton(icon: const Icon(LucideIcons.chevronLeft, color: AppColors.textPrimary), onPressed: () => popRoute(ref)),
|
||||
title: const Text('设置', style: TextStyle(fontSize: 21, fontWeight: FontWeight.w600, color: AppColors.textPrimary)),
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.86),
|
||||
leading: IconButton(
|
||||
icon: const Icon(
|
||||
LucideIcons.chevronLeft,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
onPressed: () => popRoute(ref),
|
||||
),
|
||||
title: const Text(
|
||||
'设置',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Column(children: [
|
||||
const SizedBox(height: AppTheme.sMd),
|
||||
AppMenuItem(icon: LucideIcons.bell, title: '消息通知', onTap: () => pushRoute(ref, 'notificationPrefs')),
|
||||
AppMenuItem(icon: LucideIcons.type, title: '字体大小', subtitle: '老年版', onTap: () {}),
|
||||
AppMenuItem(icon: LucideIcons.sprayCan, title: '清除缓存', onTap: () {}),
|
||||
AppMenuItem(icon: LucideIcons.info, title: '关于健康管家', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'about'})),
|
||||
AppMenuItem(icon: LucideIcons.shield, title: '隐私协议', onTap: () => pushRoute(ref, 'staticText', params: {'type': 'privacy'})),
|
||||
const SizedBox(height: 30),
|
||||
GestureDetector(
|
||||
onTap: () => _logout(context, ref),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppTheme.rXl),
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.error.withAlpha(80)),
|
||||
borderRadius: BorderRadius.circular(AppTheme.rPill),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 34),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const _SettingsHeader(),
|
||||
const SizedBox(height: 18),
|
||||
_SettingsGroup(
|
||||
title: '健康与设备',
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.bluetooth,
|
||||
title: '蓝牙设备',
|
||||
colors: const [Color(0xFF38BDF8), Color(0xFF2563EB)],
|
||||
onTap: () => pushRoute(ref, 'devices'),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.bell,
|
||||
title: '消息通知',
|
||||
colors: const [Color(0xFFA78BFA), Color(0xFF7C3AED)],
|
||||
onTap: () => pushRoute(ref, 'notificationPrefs'),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Text('退出登录', style: TextStyle(fontSize: 19, color: AppColors.error, fontWeight: FontWeight.w500)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_SettingsGroup(
|
||||
title: '使用偏好',
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.type,
|
||||
title: '字体大小',
|
||||
subtitle: '老年友好',
|
||||
colors: const [Color(0xFFFBBF24), Color(0xFFEA580C)],
|
||||
onTap: () {},
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.sprayCan,
|
||||
title: '清除缓存',
|
||||
colors: const [Color(0xFF2DD4BF), Color(0xFF0F766E)],
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_SettingsGroup(
|
||||
title: '关于',
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.info,
|
||||
title: '关于健康管家',
|
||||
colors: const [Color(0xFF60A5FA), Color(0xFF0891B2)],
|
||||
onTap: () =>
|
||||
pushRoute(ref, 'staticText', params: {'type': 'about'}),
|
||||
),
|
||||
_SettingsTile(
|
||||
icon: LucideIcons.shield,
|
||||
title: '隐私协议',
|
||||
colors: const [Color(0xFFF472B6), Color(0xFFDB2777)],
|
||||
onTap: () => pushRoute(
|
||||
ref,
|
||||
'staticText',
|
||||
params: {'type': 'privacy'},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _deleteAccount(context, ref),
|
||||
icon: const Icon(LucideIcons.trash, size: 19),
|
||||
label: const Text('删除账号'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.error,
|
||||
side: BorderSide(color: AppColors.error.withValues(alpha: 0.34)),
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w800),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _logout(context, ref),
|
||||
icon: const Icon(LucideIcons.logOut, size: 19),
|
||||
label: const Text('退出登录'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.error,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size.fromHeight(52),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w800),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteAccount(BuildContext context, WidgetRef ref) async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.rXl)),
|
||||
title: const Text('删除账号', style: TextStyle(color: AppColors.error)),
|
||||
content: const Text('此操作不可恢复,将删除所有健康数据、用药记录、饮食记录、报告等,确定继续?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')),
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确认删除', style: TextStyle(color: AppColors.error))),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ok == true) {
|
||||
await ref.read(apiClientProvider).delete('/api/user/account');
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
goRoute(ref, 'login');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _logout(BuildContext context, WidgetRef ref) async {
|
||||
final ok = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.rXl)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppTheme.rXl),
|
||||
),
|
||||
title: const Text('退出登录'),
|
||||
content: const Text('确定退出?'),
|
||||
content: const Text('确定退出当前账号?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')),
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('确定', style: TextStyle(color: AppColors.error))),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('确定', style: TextStyle(color: AppColors.error)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (ok == true) { await ref.read(authProvider.notifier).logout(); goRoute(ref, 'login'); }
|
||||
if (ok == true) {
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
goRoute(ref, 'login');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsHeader extends StatelessWidget {
|
||||
const _SettingsHeader();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFEEF2FF), Color(0xFFF7ECFF), Color(0xFFEFFBF9)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(color: Colors.white, width: 1.5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 52,
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF38BDF8), Color(0xFFC084FC)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Icon(
|
||||
LucideIcons.settings,
|
||||
color: Colors.white,
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'偏好设置',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
'管理设备、通知和账号安全',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsGroup extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
const _SettingsGroup({required this.title, required this.children});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.88),
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
border: Border.all(color: Colors.white, width: 1.4),
|
||||
boxShadow: AppColors.cardShadowLight,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
for (var i = 0; i < children.length; i++) ...[
|
||||
children[i],
|
||||
if (i != children.length - 1)
|
||||
const Divider(height: 1, color: Color(0xFFF1F3F8)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final List<Color> colors;
|
||||
final VoidCallback onTap;
|
||||
const _SettingsTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.colors,
|
||||
required this.onTap,
|
||||
this.subtitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 42,
|
||||
height: 42,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: colors,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 21),
|
||||
),
|
||||
const SizedBox(width: 13),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
subtitle!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 16,
|
||||
color: AppColors.textHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user