前端: - 新增 DietCapturePage 独立拍照识别页 - 5种消息卡片类型完整实现(数据确认/用药/饮食/报告/快捷选项) - 任务卡片区:异常警告+数据摘要+自动折叠 - 侧滑抽屉:历史对话列表+对话管理 - 运动计划:进度卡片+创建计划+每日打卡 - 报告页:拍照/相册/PDF上传+分析 - 面板按钮补全血氧/体重录入 - UI 升级:紫色主题+动画+气泡样式 - 全部迁移 Riverpod 3.x API 后端: - 新增 _UpdateMessageTypeAndMetadata,Tool Calling 自动映射消息类型 - SSE answer 事件携带 type 字段 - 提示词优化(移除"阿福",语气规则归位) - 运动计划支持 AI 创建和打卡 测试: - 新增 full_e2e_test.py 全流程测试(认证/数据CRUD/6个Agent对话/VLM/报告)
507 lines
22 KiB
Dart
507 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../providers/chat_provider.dart';
|
|
|
|
/// 对话消息列表
|
|
class ChatMessagesView extends ConsumerWidget {
|
|
final ScrollController scrollCtrl;
|
|
final List<ChatMessage> messages;
|
|
|
|
const ChatMessagesView({super.key, required this.scrollCtrl, required this.messages});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final chatState = ref.watch(chatProvider);
|
|
|
|
if (messages.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFEDEBFF),
|
|
borderRadius: BorderRadius.circular(40),
|
|
),
|
|
child: const Icon(Icons.health_and_safety, size: 40, color: Color(0xFF635BFF)),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text('开始和 AI 健康管家对话吧', style: Theme.of(context).textTheme.bodyMedium),
|
|
const SizedBox(height: 8),
|
|
Text('记录健康数据,获取专业建议', style: TextStyle(fontSize: 14, color: Colors.grey[400])),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
controller: scrollCtrl,
|
|
reverse: true,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
itemCount: messages.length,
|
|
itemBuilder: (context, index) {
|
|
final msg = messages[messages.length - 1 - index];
|
|
return _buildMessageContent(context, msg, chatState);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildMessageContent(BuildContext context, ChatMessage msg, ChatState chatState) {
|
|
final isUser = msg.isUser;
|
|
|
|
if (!isUser && chatState.isStreaming && msg.content.isEmpty) {
|
|
return _buildThinkingBubble(context, chatState.thinkingText);
|
|
}
|
|
|
|
switch (msg.type) {
|
|
case MessageType.dataConfirm:
|
|
return _buildDataConfirmCard(context, msg);
|
|
case MessageType.medicationConfirm:
|
|
return _buildMedicationConfirmCard(context, msg);
|
|
case MessageType.dietAnalysis:
|
|
return _buildDietAnalysisCard(context, msg);
|
|
case MessageType.reportAnalysis:
|
|
return _buildReportAnalysisCard(context, msg);
|
|
case MessageType.quickOptions:
|
|
return _buildQuickOptionsCard(context, msg);
|
|
default:
|
|
return _buildTextBubble(context, msg);
|
|
}
|
|
}
|
|
|
|
Widget _buildThinkingBubble(BuildContext context, String? thinkingText) {
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), topRight: Radius.circular(20), bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: const [BoxShadow(color: Color(0xFF635BFF), blurRadius: 4, offset: Offset(0, 2))],
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 24,
|
|
height: 24,
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFEDEBFF),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF635BFF)),
|
|
),
|
|
const SizedBox(width: 10),
|
|
const Text('正在分析...', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTextBubble(BuildContext context, ChatMessage msg) {
|
|
final isUser = msg.isUser;
|
|
return Align(
|
|
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: isUser ? const Color(0xFF635BFF) : const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(isUser ? 20 : 4),
|
|
topRight: Radius.circular(isUser ? 4 : 20),
|
|
bottomLeft: const Radius.circular(20),
|
|
bottomRight: const Radius.circular(20),
|
|
),
|
|
border: isUser ? null : Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: isUser ? [] : [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (isUser)
|
|
Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4))
|
|
else
|
|
MarkdownBody(
|
|
data: msg.content,
|
|
selectable: true,
|
|
styleSheet: MarkdownStyleSheet(
|
|
p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5),
|
|
h1: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
|
h2: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
|
code: const TextStyle(fontSize: 14, backgroundColor: Colors.grey),
|
|
),
|
|
),
|
|
if (!isUser && !msg.content.isEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 10),
|
|
child: Row(children: [
|
|
const CircleAvatar(radius: 10, backgroundColor: Color(0xFFEDEBFF), child: Icon(Icons.chat_bubble_outline, size: 14, color: Color(0xFF635BFF))),
|
|
const SizedBox(width: 6),
|
|
Text('健康管家', style: TextStyle(fontSize: 12, color: Colors.grey[400])),
|
|
const SizedBox(width: 4),
|
|
Text('仅供参考', style: TextStyle(fontSize: 11, color: Colors.grey[300])),
|
|
]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDataConfirmCard(BuildContext context, ChatMessage msg) {
|
|
final meta = msg.metadata;
|
|
final metricType = meta?['type'] as String? ?? '';
|
|
final value = meta?['value'] as String? ?? '';
|
|
final abnormal = meta?['abnormal'] as bool? ?? false;
|
|
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFFF5F3FF),
|
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
|
),
|
|
child: Row(children: [
|
|
const Icon(Icons.check_circle, size: 20, color: Color(0xFF43A047)),
|
|
const SizedBox(width: 8),
|
|
const Text('已记录', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF43A047))),
|
|
]),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(children: [
|
|
Row(children: [
|
|
Text(
|
|
_getMetricIcon(metricType),
|
|
style: const TextStyle(fontSize: 24),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Text(_getMetricName(metricType), style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
const SizedBox(height: 4),
|
|
Text(value, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: abnormal ? const Color(0xFFE53935) : const Color(0xFF1A1A1A))),
|
|
]),
|
|
const Spacer(),
|
|
if (abnormal) const Icon(Icons.warning_amber, size: 20, color: Color(0xFFE53935)),
|
|
]),
|
|
if (abnormal)
|
|
const Padding(
|
|
padding: EdgeInsets.only(top: 12),
|
|
child: Text('⚠️ 数值超出正常范围,请关注', style: TextStyle(fontSize: 14, color: Color(0xFFE53935))),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: () {},
|
|
child: const Text('编辑'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: const Color(0xFF635BFF),
|
|
side: const BorderSide(color: Color(0xFF635BFF)),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () {},
|
|
child: const Text('确认'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF635BFF),
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
),
|
|
),
|
|
),
|
|
]),
|
|
]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMedicationConfirmCard(BuildContext context, ChatMessage msg) {
|
|
final meta = msg.metadata;
|
|
final name = meta?['name'] as String? ?? '';
|
|
final dosage = meta?['dosage'] as String? ?? '';
|
|
final time = meta?['time'] as String? ?? '';
|
|
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(children: [
|
|
Row(children: [
|
|
const Text('💊', style: TextStyle(fontSize: 28)),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Text(name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
|
if (dosage.isNotEmpty) Text(dosage, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
if (time.isNotEmpty) Text('每天 $time', style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
]),
|
|
),
|
|
]),
|
|
const SizedBox(height: 16),
|
|
const Text('需要调整吗?', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
const SizedBox(height: 12),
|
|
Row(children: [
|
|
Expanded(child: _medBtn('确认', Icons.check, Colors.white, const Color(0xFF635BFF))),
|
|
const SizedBox(width: 8),
|
|
Expanded(child: _medBtn('修改时间', Icons.access_time, const Color(0xFF635BFF), Colors.white)),
|
|
const SizedBox(width: 8),
|
|
Expanded(child: _medBtn('改剂量', Icons.edit, const Color(0xFF635BFF), Colors.white)),
|
|
]),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _medBtn(String label, IconData icon, Color textColor, Color bgColor) {
|
|
return ElevatedButton(
|
|
onPressed: () {},
|
|
child: Row(children: [Icon(icon, size: 16), const SizedBox(width: 4), Text(label, style: TextStyle(fontSize: 12))]),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: bgColor,
|
|
foregroundColor: textColor,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDietAnalysisCard(BuildContext context, ChatMessage msg) {
|
|
final meta = msg.metadata;
|
|
final foods = meta?['foods'] as List? ?? [];
|
|
final totalCalories = meta?['totalCalories'] as int? ?? 0;
|
|
final rating = meta?['rating'] as int? ?? 0;
|
|
final warnings = meta?['warnings'] as List? ?? [];
|
|
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
const Text('🍽️ 饮食分析', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
const SizedBox(height: 12),
|
|
Column(children: foods.map((food) {
|
|
final f = food as Map? ?? {};
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
child: Row(children: [
|
|
Text(f['name'] as String? ?? '', style: const TextStyle(fontSize: 14)),
|
|
const Spacer(),
|
|
Text('${f['calories'] ?? 0} kcal', style: TextStyle(fontSize: 14, color: Colors.grey[500])),
|
|
]),
|
|
);
|
|
}).toList()),
|
|
const SizedBox(height: 12),
|
|
Row(children: [
|
|
const Text('总热量', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
const Spacer(),
|
|
Text('$totalCalories kcal', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
]),
|
|
const SizedBox(height: 12),
|
|
Row(children: [
|
|
const Text('健康评分', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
const SizedBox(width: 8),
|
|
Row(children: List.generate(5, (i) => Icon(Icons.star, size: 16, color: i < rating ? const Color(0xFFFFB800) : Colors.grey[300]))),
|
|
]),
|
|
if (warnings.isNotEmpty) ...[
|
|
const SizedBox(height: 12),
|
|
...warnings.map((w) => Padding(
|
|
padding: const EdgeInsets.only(bottom: 4),
|
|
child: Text('⚠️ $w', style: TextStyle(fontSize: 14, color: const Color(0xFFE53935))),
|
|
)),
|
|
],
|
|
const SizedBox(height: 12),
|
|
const Text('建议:饮食均衡,多吃蔬菜水果', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildReportAnalysisCard(BuildContext context, ChatMessage msg) {
|
|
final meta = msg.metadata;
|
|
final reportType = meta?['type'] as String? ?? '';
|
|
final indicators = meta?['indicators'] as List? ?? [];
|
|
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Row(children: [
|
|
const Text('📋', style: TextStyle(fontSize: 20)),
|
|
const SizedBox(width: 8),
|
|
Text(reportType, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|
]),
|
|
const SizedBox(height: 12),
|
|
const Text('AI 预解读结果', style: TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
const SizedBox(height: 8),
|
|
Column(children: indicators.map((ind) {
|
|
final i = ind as Map? ?? {};
|
|
final name = i['name'] as String? ?? '';
|
|
final value = i['value'] as String? ?? '';
|
|
final status = i['status'] as String? ?? 'normal';
|
|
Color statusColor;
|
|
switch (status) {
|
|
case 'high': statusColor = const Color(0xFFE53935); break;
|
|
case 'low': statusColor = const Color(0xFFF9A825); break;
|
|
default: statusColor = const Color(0xFF43A047);
|
|
}
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
child: Row(children: [
|
|
Expanded(child: Text(name, style: const TextStyle(fontSize: 14))),
|
|
Text(value, style: TextStyle(fontSize: 14, color: statusColor, fontWeight: FontWeight.w600)),
|
|
const SizedBox(width: 8),
|
|
Icon(status == 'normal' ? Icons.check_circle : Icons.warning_amber, size: 16, color: statusColor),
|
|
]),
|
|
);
|
|
}).toList()),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEF3C7),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Text('⚠️ AI 预解读,待医生确认', style: TextStyle(fontSize: 13, color: Color(0xFFD97706))),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Center(
|
|
child: OutlinedButton(
|
|
onPressed: () {},
|
|
child: const Text('查看原始图片'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: const Color(0xFF635BFF),
|
|
side: const BorderSide(color: Color(0xFF635BFF)),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
),
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildQuickOptionsCard(BuildContext context, ChatMessage msg) {
|
|
final meta = msg.metadata;
|
|
final options = meta?['options'] as List? ?? [];
|
|
|
|
return Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.85),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFEFEFF),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: const Color(0xFFE8E6FF), width: 1.5),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 4, offset: const Offset(0, 2))],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(children: [
|
|
Text(msg.content, style: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A))),
|
|
const SizedBox(height: 12),
|
|
Wrap(spacing: 8, runSpacing: 8, children: options.map((opt) {
|
|
final o = opt as Map? ?? {};
|
|
return ElevatedButton(
|
|
onPressed: () {},
|
|
child: Text(o['label'] as String? ?? '', style: const TextStyle(fontSize: 14)),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFFF5F3FF),
|
|
foregroundColor: const Color(0xFF635BFF),
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
);
|
|
}).toList()),
|
|
]),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getMetricIcon(String type) {
|
|
switch (type.toLowerCase()) {
|
|
case 'blood_pressure': return '🩺';
|
|
case 'heart_rate': return '💓';
|
|
case 'glucose': return '💉';
|
|
case 'spo2': return '🫁';
|
|
case 'weight': return '⚖️';
|
|
default: return '📊';
|
|
}
|
|
}
|
|
|
|
String _getMetricName(String type) {
|
|
switch (type.toLowerCase()) {
|
|
case 'blood_pressure': return '血压';
|
|
case 'heart_rate': return '心率';
|
|
case 'glucose': return '血糖';
|
|
case 'spo2': return '血氧';
|
|
case 'weight': return '体重';
|
|
default: return '健康指标';
|
|
}
|
|
}
|
|
}
|