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:
MingNian
2026-07-09 15:04:02 +08:00
parent 1c020b8ae5
commit d82e006cf4
37 changed files with 1842 additions and 2096 deletions

View File

@@ -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,