fix: VLM识别修复 + 饮食CRUD + 侧边栏美化 + UI优化
- VLM: ChatMessage.Content string→object, 修复视觉content双重序列化 - 饮食: diet/medication端点 record→手动JSON解析, 修复循环引用 - 饮食记录: 左滑删除 + 去评分 + AI饮食评语(DeepSeek) - 侧边栏: 功能区统一Row+Expanded, 服务包compact模式 - 历史对话: 点击加载历史消息 - 登录页: 居中布局, 去底部空白 - UI: 主色加深#6C5CE7, maxWidth:1024防图片超大
This commit is contained in:
@@ -119,51 +119,47 @@ class HealthDrawer extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// ════════════ 功能区(横向排布)════════════
|
||||
// ════════════ 功能区(横向均布)════════════
|
||||
_SectionCard(
|
||||
color: Colors.white,
|
||||
gradientColors: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0A060).withAlpha(15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.apps_rounded, size: 13, color: Color(0xFFF0A060)),
|
||||
SizedBox(width: 4),
|
||||
Text('功能', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))),
|
||||
]),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 14, 16, 6),
|
||||
child: Row(children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0A060).withAlpha(15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_FeatureChip(icon: Icons.description_outlined, label: '报告管理', onTap: () => pushRoute(ref, 'reports')),
|
||||
_FeatureChip(icon: Icons.calendar_today_outlined, label: '健康日历', onTap: () => pushRoute(ref, 'calendar')),
|
||||
_FeatureChip(icon: Icons.restaurant_outlined, label: '饮食记录', onTap: () => pushRoute(ref, 'dietRecords')),
|
||||
_FeatureChip(icon: Icons.event_note_outlined, label: '复查随访', onTap: () => pushRoute(ref, 'followups')),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.apps_rounded, size: 13, color: Color(0xFFF0A060)),
|
||||
SizedBox(width: 4),
|
||||
Text('功能', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFFF0A060))),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 12, 14),
|
||||
child: Row(children: [
|
||||
_FeatureChip(icon: Icons.description_outlined, label: '报告管理', onTap: () => pushRoute(ref, 'reports')),
|
||||
_FeatureChip(icon: Icons.calendar_today_outlined, label: '健康日历', onTap: () => pushRoute(ref, 'calendar')),
|
||||
_FeatureChip(icon: Icons.restaurant_outlined, label: '饮食记录', onTap: () => pushRoute(ref, 'dietRecords')),
|
||||
_FeatureChip(icon: Icons.event_note_outlined, label: '复查随访', onTap: () => pushRoute(ref, 'followups')),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// ════════════ 产品服务包 ════════════
|
||||
const ServicePackageCard(),
|
||||
const ServicePackageCard(compact: true),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
@@ -196,7 +192,7 @@ class HealthDrawer extends ConsumerWidget {
|
||||
),
|
||||
]),
|
||||
),
|
||||
_buildConversationList(ref, conversations),
|
||||
_buildConversationList(context, ref, conversations),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -253,7 +249,7 @@ class HealthDrawer extends ConsumerWidget {
|
||||
return metric.toString();
|
||||
}
|
||||
|
||||
Widget _buildConversationList(WidgetRef ref, AsyncValue<List<ConversationItem>> conversations) {
|
||||
Widget _buildConversationList(BuildContext context, WidgetRef ref, AsyncValue<List<ConversationItem>> conversations) {
|
||||
return conversations.when(
|
||||
data: (items) {
|
||||
if (items.isEmpty) {
|
||||
@@ -268,7 +264,13 @@ class HealthDrawer extends ConsumerWidget {
|
||||
padding: const EdgeInsets.fromLTRB(8, 6, 8, 14),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: items.map((item) => _ConversationItem(item: item)).toList(),
|
||||
children: items.map((item) => _ConversationItem(
|
||||
item: item,
|
||||
onTap: () {
|
||||
ref.read(chatProvider.notifier).loadConversation(item.id);
|
||||
Navigator.of(context).pop(); // 关闭抽屉
|
||||
},
|
||||
)).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -394,22 +396,20 @@ class _FeatureChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: _c.withAlpha(10),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(icon, size: 17, color: _c),
|
||||
const SizedBox(width: 6),
|
||||
Text(label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: _c)),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(icon, size: 18, color: _c),
|
||||
const SizedBox(height: 6),
|
||||
Text(label, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFF666666))),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -423,8 +423,9 @@ class _FeatureChip extends StatelessWidget {
|
||||
|
||||
class _ConversationItem extends StatelessWidget {
|
||||
final ConversationItem item;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _ConversationItem({required this.item});
|
||||
const _ConversationItem({required this.item, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -436,6 +437,7 @@ class _ConversationItem extends StatelessWidget {
|
||||
border: Border.all(color: const Color(0xFFEEEEEE)),
|
||||
),
|
||||
child: ListTile(
|
||||
onTap: onTap,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
|
||||
leading: Container(
|
||||
width: 32, height: 32,
|
||||
|
||||
@@ -156,14 +156,109 @@ final List<ServicePackage> servicePackages = [
|
||||
),
|
||||
];
|
||||
|
||||
/// 服务包卡片 —— 展示在"我的"页面中
|
||||
/// 服务包卡片
|
||||
class ServicePackageCard extends ConsumerWidget {
|
||||
const ServicePackageCard({super.key});
|
||||
final bool compact;
|
||||
const ServicePackageCard({super.key, this.compact = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final package = servicePackages.first;
|
||||
|
||||
// 构建服务图标网格
|
||||
final services = package.services.take(8).toList();
|
||||
final serviceRows = <Widget>[];
|
||||
for (var i = 0; i < services.length; i += 4) {
|
||||
final rowItems = services.skip(i).take(4).toList();
|
||||
serviceRows.add(Padding(
|
||||
padding: EdgeInsets.only(top: i > 0 ? 12 : 0),
|
||||
child: Row(
|
||||
children: rowItems.map((item) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFFFF8EE),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(item.icon, size: 20, color: const Color(0xFFF5A623)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(item.label,
|
||||
style: const TextStyle(fontSize: 11, color: AppTheme.textSub),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
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: 16, 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: 13, color: AppTheme.textSub)),
|
||||
const SizedBox(width: 2),
|
||||
Icon(Icons.chevron_right, size: 18, color: AppTheme.textHint),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
// VIP 标签
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(colors: [const Color(0xFFF5A623).withAlpha(200), const Color(0xFFE8930C)]),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('VIP', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
SizedBox(width: 4),
|
||||
Text('VIP 产品权益', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: Colors.white)),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
// 服务图标网格
|
||||
...serviceRows,
|
||||
const SizedBox(height: 12),
|
||||
// 查看更多
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Text('查看更多服务包', style: TextStyle(fontSize: 13, color: AppTheme.primary, fontWeight: FontWeight.w500)),
|
||||
Icon(Icons.chevron_right, size: 16, color: AppTheme.primary),
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (compact) return card;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
@@ -177,161 +272,7 @@ class ServicePackageCard extends ConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: 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: 16,
|
||||
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: 13,
|
||||
color: AppTheme.textSub,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 18,
|
||||
color: AppTheme.textHint,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// VIP 标签
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
const Color(0xFFF5A623).withAlpha(200),
|
||||
const Color(0xFFE8930C),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'VIP',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
package.subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// 服务图标网格
|
||||
Wrap(
|
||||
spacing: 0,
|
||||
runSpacing: 12,
|
||||
children: package.services.take(8).map((item) {
|
||||
return SizedBox(
|
||||
width: (MediaQuery.of(context).size.width - 48 - 36) / 4,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF8EE),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
item.icon,
|
||||
size: 20,
|
||||
color: const Color(0xFFF5A623),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppTheme.textSub,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 查看更多
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () => pushRoute(ref, 'servicePackageDetail', params: {'id': package.id}),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'查看更多服务包',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppTheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 16,
|
||||
color: AppTheme.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: card,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user