Compare commits

...

2 Commits

Author SHA1 Message Date
MingNian
80540e2191 fix: 健康档案保存按钮移至右上角 + 5页面补充返回按钮 + API地址修复
- 健康档案: 保存按钮移到AppBar右侧,删除底部按钮和绿色成功SnackBar
- 5个缺失页面补充左上角返回按钮: 健康概览/编辑用药/报告管理/报告详情/报告解读
- api_client.dart: 默认baseUrl改为192.168.1.29 (支持WiFi直连)
2026-06-22 15:58:59 +08:00
MingNian
6ce010e46a feat: AI回复格式约束 + 前端清洗器增强
- prompt: 禁止**粗体*斜体等内联标记、HTML标签、表格,标题仅限###
- 前端_cleanAiText: 移除$N占位符、HTML标签、多余#标题降级
- 确认卡片/结构化消息不受影响
2026-06-22 14:14:42 +08:00
8 changed files with 60 additions and 48 deletions

View File

@@ -34,6 +34,16 @@ public sealed class PromptManager
-
- 使
- AI
- 使 ******~~线~~ Markdown
- 使 HTML
- 使 Markdown |--|--|
- 使 ### 使 #######
- 使 -
- 120 mmHg6.2 mmol/L
- $
- MedicalBoundaryRules
""";
private const string DefaultPrompt = """

View File

@@ -4,9 +4,11 @@ import 'package:dio/dio.dart';
import 'local_database.dart';
/// API 基础地址。可通过 --dart-define=API_BASE_URL=http://host:5000 覆盖。
/// Android 真机 USB: 先执行 adb reverse tcp:5000 tcp:5000 然后 localhost 即可
/// Android 真机 WiFi: flutter run --dart-define=API_BASE_URL=http://电脑IP:5000
const String baseUrl = String.fromEnvironment(
'API_BASE_URL',
defaultValue: 'http://localhost:5000',
defaultValue: 'http://192.168.1.29:5000',
);
class ApiException implements Exception {

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart';
import '../../providers/auth_provider.dart';
import '../../providers/data_providers.dart';
@@ -247,7 +248,11 @@ class _TrendPageState extends ConsumerState<TrendPage> {
@override
Widget build(BuildContext context) {
return GradientScaffold(
appBar: AppBar(title: const Text('健康概览'), centerTitle: true),
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
title: const Text('健康概览'),
centerTitle: true,
),
floatingActionButton: FloatingActionButton(
onPressed: _showAddDialog,
backgroundColor: _color,

View File

@@ -1243,10 +1243,16 @@ class ChatMessagesView extends ConsumerWidget {
/// 清理 AI 返回文本中的异常占位符Markdown 格式交给 MarkdownBody 渲染)
static String _cleanAiText(String text) {
return text
.replaceAll('\$1', '') // AI 偶尔输出 $1 占位符(正则残留)
.replaceAll(RegExp(r'\n{3,}'), '\n\n')
.trim();
var t = text;
// 移除 $1、$2 等占位符
t = t.replaceAll(RegExp(r'\$\d+'), '');
// 移除残留 HTML 标签
t = t.replaceAll(RegExp(r'<[^>]+>'), '');
// 行首 # 超过 3 个降为 ###
t = t.replaceAllMapped(RegExp(r'^#{4,}\s', multiLine: true), (_) => '### ');
// 压缩多余空行
t = t.replaceAll(RegExp(r'\n{3,}'), '\n\n');
return t.trim();
}
String _freqLabel(String freq) {

View File

@@ -138,7 +138,10 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
@override
Widget build(BuildContext context) {
return GradientScaffold(
appBar: AppBar(title: Text(widget.id != null ? '编辑用药' : '添加用药')),
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
title: Text(widget.id != null ? '编辑用药' : '添加用药'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [

View File

@@ -1381,14 +1381,6 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
'familyHistory': _familyCtrl.text,
});
await ref.read(authProvider.notifier).refreshProfile();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('保存成功'),
backgroundColor: AppColors.success,
),
);
}
} catch (_) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
@@ -1438,6 +1430,21 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
icon: const Icon(Icons.arrow_back, color: AppColors.textPrimary),
onPressed: () => popRoute(ref),
),
actions: [
_saving
? const Padding(
padding: EdgeInsets.only(right: 16),
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
),
)
: TextButton(
onPressed: _save,
child: const Text('保存', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
@@ -1525,36 +1532,6 @@ class _HealthArchivePageState extends ConsumerState<HealthArchivePage> {
const SizedBox(height: 12),
_F('家族病史', _familyCtrl, hint: '如: 父亲冠心病'),
]),
const SizedBox(height: 24),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
gradient: AppColors.primaryGradient,
),
padding: const EdgeInsets.all(1.5),
child: SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: _saving ? null : _save,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AppColors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(13),
),
elevation: 0,
),
child: Text(
_saving ? '保存中...' : '保存档案',
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
),
),
),
const SizedBox(height: 40),
],
),

View File

@@ -31,7 +31,10 @@ class _AiAnalysisPageState extends ConsumerState<AiAnalysisPage> {
if (analysis == null) {
return GradientScaffold(
appBar: AppBar(title: const Text('报告解读')),
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
title: const Text('报告解读'),
),
body: const Center(
child: CircularProgressIndicator(color: AppColors.primary),
),

View File

@@ -351,7 +351,10 @@ class ReportListPage extends ConsumerWidget {
if (state.isAnalyzing) {
return GradientScaffold(
appBar: AppBar(title: const Text('报告管理')),
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
title: const Text('报告管理'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -720,7 +723,10 @@ class ReportOriginalPage extends StatelessWidget {
Widget build(BuildContext context) {
final imageUrl = _absoluteUrl(url);
return GradientScaffold(
appBar: AppBar(title: Text(title)),
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context)),
title: Text(title),
),
body: Center(
child: InteractiveViewer(
minScale: 0.7,