refactor: 死代码清理 + 趋势图重构 + 侧边栏交互优化 + 智能体卡片去延迟
- 死代码清理: 删除 AppCard/AppMenuItem/AppTabChip/AppButtons 等 4 个未使用 Widget 文件; 清理 common_widgets/enterprise_widgets/app_status_badge 中未使用 class; 移除 HealthService 等未使用方法; 后端移除 ExerciseService.CreateFromItemsAsync/HealthArchiveService.GetOrCreateAsync/MedicationAgentHandler 死分支/ChatRequest DTO - 趋势图: 直线折线 + 渐变填充 + 阴影; 去网格; X 轴日+月份; Y 轴整数刻度最多 4 个; 点击数据点/录入记录显示上方浮层; 选中点变实心 - 侧边栏: 对话记录改单选删除(灰底高亮); 操作栏浮层修复触摸问题; 常用功能/健康仪表盘间距收紧; _Panel 去外框 - 智能体: 欢迎卡片去掉 400ms 延迟; 胶囊去阴影 - 其他: api_client IP 适配; 多页面 UI 微调; 新增 chat_provider/prelaunch_guardrails 测试
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../core/app_colors.dart';
|
||||
import '../core/app_design_tokens.dart';
|
||||
import '../core/app_module_visuals.dart';
|
||||
import '../core/app_theme.dart';
|
||||
import '../core/navigation_provider.dart';
|
||||
@@ -721,11 +722,7 @@ class _ExercisePlanPageState extends ConsumerState<ExercisePlanPage> {
|
||||
} catch (e) {
|
||||
debugPrint('[ExercisePlan] 打卡失败: $e');
|
||||
if (mounted) {
|
||||
AppToast.show(
|
||||
context,
|
||||
'只能打卡今天的运动任务',
|
||||
type: AppToastType.error,
|
||||
);
|
||||
AppToast.show(context, '只能打卡今天的运动任务', type: AppToastType.error);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _busyItems.remove(itemId));
|
||||
@@ -1304,11 +1301,7 @@ class _ExercisePlanDetailPageState
|
||||
} catch (e) {
|
||||
debugPrint('[ExercisePlanDetail] 打卡失败: $e');
|
||||
if (mounted) {
|
||||
AppToast.show(
|
||||
context,
|
||||
'只能打卡今天的运动任务',
|
||||
type: AppToastType.error,
|
||||
);
|
||||
AppToast.show(context, '只能打卡今天的运动任务', type: AppToastType.error);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _busyItems.remove(itemId));
|
||||
@@ -2462,7 +2455,7 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.errorLight,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
@@ -2506,12 +2499,10 @@ class _Section extends StatelessWidget {
|
||||
};
|
||||
@override
|
||||
Widget build(BuildContext c) => Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.border, width: 1.1),
|
||||
boxShadow: [AppTheme.shadowLight],
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -2519,26 +2510,26 @@ class _Section extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: _color.withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Icon(icon, size: 20, color: _color),
|
||||
child: Icon(icon, size: 18, color: _color),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 12),
|
||||
...fields,
|
||||
],
|
||||
),
|
||||
@@ -2572,15 +2563,15 @@ class _F extends StatelessWidget {
|
||||
vertical: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: AppColors.borderLight),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: AppColors.borderLight),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: Color(0xFF60A5FA)),
|
||||
),
|
||||
),
|
||||
@@ -2610,7 +2601,7 @@ class _GenderF extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
),
|
||||
child: Row(
|
||||
@@ -2641,7 +2632,7 @@ class _GenderChip extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? const Color(0xFF2563EB) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
@@ -2685,15 +2676,15 @@ class _F2 extends StatelessWidget {
|
||||
vertical: 8,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: AppColors.borderLight),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: AppColors.borderLight),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
borderSide: const BorderSide(color: Color(0xFF60A5FA)),
|
||||
),
|
||||
),
|
||||
@@ -2723,7 +2714,7 @@ class _DateF extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
@@ -2774,7 +2765,7 @@ class _DateF2 extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
@@ -2824,7 +2815,7 @@ class _AddBtn extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: AppRadius.smBorder,
|
||||
border: Border.all(color: const Color(0xFFBFDBFE)),
|
||||
),
|
||||
child: const Row(
|
||||
@@ -3366,13 +3357,119 @@ class _Legend extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
const Map<String, String> _extraStaticTextTitles = {
|
||||
'personalInfoList': '个人信息收集清单',
|
||||
'thirdPartySdkList': '第三方 SDK 共享清单',
|
||||
};
|
||||
|
||||
const Map<String, String> _extraStaticTextContents = {
|
||||
'personalInfoList': '''## 个人信息收集清单
|
||||
|
||||
更新日期:2026年7月9日
|
||||
|
||||
本清单用于说明小脉健康 App 在当前功能范围内可能收集的个人信息类型、使用目的和使用场景。实际启用情况以正式部署配置和您实际使用的功能为准。
|
||||
|
||||
### 一、账号与登录信息
|
||||
|
||||
- 信息内容:手机号、登录状态、账号标识、验证码校验结果。
|
||||
- 使用目的:完成注册、登录、身份识别、账号安全校验和账号注销。
|
||||
- 使用场景:登录、注册、刷新登录状态、删除账号。
|
||||
|
||||
### 二、个人资料和健康档案
|
||||
|
||||
- 信息内容:姓名或昵称、性别、出生日期、头像、过敏史、慢病史、家族史、手术信息、饮食禁忌等。
|
||||
- 使用目的:建立健康档案,辅助 AI 健康咨询、医生咨询、报告解读和健康管理。
|
||||
- 使用场景:完善资料、健康档案、AI 对话、医生端查看。
|
||||
|
||||
### 三、健康数据
|
||||
|
||||
- 信息内容:血压、心率、血糖、血氧、体重、记录时间、数据来源、异常状态。
|
||||
- 使用目的:记录和展示健康趋势,生成健康提醒,辅助 AI 分析和医生咨询。
|
||||
- 使用场景:手动录入、蓝牙血压计同步、健康概览、通知提醒、AI 对话确认录入。
|
||||
|
||||
### 四、用药、运动、饮食和日历数据
|
||||
|
||||
- 信息内容:药品名称、剂量、频次、服药时间、打卡记录、运动计划、饮食记录、热量估算、日程提醒。
|
||||
- 使用目的:提供用药管理、运动计划、饮食识别、健康提醒和日历管理。
|
||||
- 使用场景:用药管理、服药打卡、运动计划、饮食拍照识别、通知中心。
|
||||
|
||||
### 五、报告、图片和文件信息
|
||||
|
||||
- 信息内容:您主动上传的检查报告图片、聊天图片、PDF 文件、AI 识别结果、报告摘要和审核状态。
|
||||
- 使用目的:完成报告管理、报告解读、图片识别、AI 对话附件分析。
|
||||
- 使用场景:上传报告、拍照识别、AI 聊天发送照片或 PDF。
|
||||
|
||||
### 六、蓝牙设备信息
|
||||
|
||||
- 信息内容:设备名称、设备标识、设备类型、服务 UUID、最近同步时间、短期去重记录。
|
||||
- 使用目的:发现、绑定和同步支持的健康设备。
|
||||
- 使用场景:蓝牙设备扫描、血压计绑定、血压数据同步。
|
||||
|
||||
### 七、设备、日志和网络信息
|
||||
|
||||
- 信息内容:设备型号、系统版本、App 版本、接口请求状态、错误信息、网络状态。
|
||||
- 使用目的:功能适配、问题排查、服务安全、性能优化和合规审计。
|
||||
- 使用场景:App 使用过程中的接口访问、异常处理、故障排查。
|
||||
''',
|
||||
'thirdPartySdkList': '''## 第三方 SDK 共享清单
|
||||
|
||||
更新日期:2026年7月9日
|
||||
|
||||
本清单用于说明小脉健康 App 当前可能接入或依赖的第三方 SDK、外部接口和服务能力。正式上线前会根据实际启用的服务商、域名和资质信息继续更新。
|
||||
|
||||
### 一、AI 大模型和视觉识别服务
|
||||
|
||||
- 使用目的:AI 健康咨询、报告解读、图片识别、文本生成和内容理解。
|
||||
- 可能共享的信息:您主动输入的对话内容、上传的图片或 PDF、健康档案摘要、需要 AI 分析的健康数据。
|
||||
- 使用场景:AI 对话、拍照识别、上传报告、报告解读。
|
||||
- 说明:实际服务商和接口地址以正式环境配置为准。
|
||||
|
||||
### 二、短信验证服务
|
||||
|
||||
- 使用目的:发送登录或注册验证码。
|
||||
- 可能共享的信息:手机号、验证码发送状态、必要的风控信息。
|
||||
- 使用场景:手机号登录、注册、身份验证。
|
||||
- 说明:当前正式短信服务仍待接入,正式启用后补充服务商信息。
|
||||
|
||||
### 三、文件存储或服务器存储服务
|
||||
|
||||
- 使用目的:保存您主动上传的报告、图片和 PDF 文件。
|
||||
- 可能共享的信息:文件内容、文件名称、上传时间、账号标识。
|
||||
- 使用场景:报告管理、AI 聊天附件、图片识别。
|
||||
- 说明:当前仍处于本地和开发环境阶段,正式服务器和域名确定后补充公网信息。
|
||||
|
||||
### 四、实时通信服务
|
||||
|
||||
- 使用目的:支持医生咨询消息、AI 流式回复和实时状态更新。
|
||||
- 可能共享的信息:消息内容、会话标识、发送时间、账号标识。
|
||||
- 使用场景:AI 对话、医生咨询、通知状态更新。
|
||||
|
||||
### 五、系统能力和开源组件
|
||||
|
||||
- 使用目的:蓝牙扫描、相机/相册选择、文件选择、图表展示、Markdown 渲染、网络请求等 App 基础能力。
|
||||
- 可能涉及的信息:蓝牙设备信息、您主动选择的图片或文件、网络请求状态。
|
||||
- 使用场景:蓝牙设备、上传照片、上传 PDF、健康趋势、合规文本展示。
|
||||
|
||||
我们不会向第三方出售您的个人信息。涉及个人信息处理的第三方服务,会在实现相关功能所必需的范围内使用,并在正式上线前结合实际服务商完善披露信息。
|
||||
''',
|
||||
};
|
||||
|
||||
String staticTextTitle(String type) => _extraStaticTextTitles[type] ?? '';
|
||||
|
||||
String staticTextContent(String type) => _extraStaticTextContents[type] ?? '';
|
||||
|
||||
/// 静态文本页
|
||||
class StaticTextPage extends ConsumerWidget {
|
||||
final String type;
|
||||
const StaticTextPage({super.key, required this.type});
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final titles = {'privacy': '隐私协议', 'terms': '服务协议', 'about': '关于小脉健康'};
|
||||
final titles = {
|
||||
'privacy': '隐私协议',
|
||||
'terms': '服务协议',
|
||||
'about': '关于小脉健康',
|
||||
..._extraStaticTextTitles,
|
||||
};
|
||||
final contents = {
|
||||
'privacy': '''## 小脉健康隐私政策
|
||||
|
||||
@@ -3676,6 +3773,7 @@ class StaticTextPage extends ConsumerWidget {
|
||||
### 版权声明
|
||||
© 2025-2026 小脉健康团队。保留所有权利。
|
||||
本软件受中华人民共和国著作权法保护。''',
|
||||
..._extraStaticTextContents,
|
||||
};
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
|
||||
Reference in New Issue
Block a user