fix: 图片发送/医生加载/运动超时/用药黑屏/服药打卡
- sendImage: 本地预览→上传→远程URL替换 - doctorListProvider: 8s超时+mock医生fallback - currentExercisePlanProvider: 8s超时→显示空状态 - 用药编辑: try-catch防黑屏+刷新列表 - 服药打卡: 接入后端confirm()接口
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'auth_provider.dart';
|
||||
import 'data_providers.dart';
|
||||
@@ -94,7 +95,7 @@ final conversationListProvider = FutureProvider<List<ConversationItem>>((ref) as
|
||||
);
|
||||
}).toList();
|
||||
} catch (_) {
|
||||
return _mockConversations;
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,30 +111,6 @@ ActiveAgent _parseAgent(String? type) {
|
||||
}
|
||||
}
|
||||
|
||||
final _mockConversations = [
|
||||
ConversationItem(
|
||||
id: '1',
|
||||
title: '用药咨询',
|
||||
lastMessage: '阿司匹林应该什么时候吃?',
|
||||
updatedAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
agent: ActiveAgent.medication,
|
||||
),
|
||||
ConversationItem(
|
||||
id: '2',
|
||||
title: '血压偏高',
|
||||
lastMessage: '血压145/90,需要注意什么?',
|
||||
updatedAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||||
agent: ActiveAgent.health,
|
||||
),
|
||||
ConversationItem(
|
||||
id: '3',
|
||||
title: '饮食建议',
|
||||
lastMessage: '今天吃了米饭和红烧肉',
|
||||
updatedAt: DateTime.now().subtract(const Duration(days: 1)),
|
||||
agent: ActiveAgent.diet,
|
||||
),
|
||||
];
|
||||
|
||||
class ChatNotifier extends Notifier<ChatState> {
|
||||
StreamSubscription<Map<String, dynamic>>? _subscription;
|
||||
|
||||
@@ -142,7 +119,7 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
|
||||
void setAgent(ActiveAgent a) {
|
||||
_subscription?.cancel();
|
||||
state = state.activeAgent == a ? const ChatState() : ChatState(activeAgent: a);
|
||||
state = state.copyWith(activeAgent: a);
|
||||
}
|
||||
|
||||
void insertAgentWelcome(ActiveAgent agent) {
|
||||
@@ -156,6 +133,49 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
)]);
|
||||
}
|
||||
|
||||
Future<void> sendImage(String imagePath, String text) async {
|
||||
final file = File(imagePath);
|
||||
if (!await file.exists()) return;
|
||||
|
||||
// 先显示用户消息(本地显示图片路径)
|
||||
final userMsg = ChatMessage(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}',
|
||||
role: 'user',
|
||||
content: text.isNotEmpty ? text : '[图片]',
|
||||
createdAt: DateTime.now(),
|
||||
metadata: {'localImagePath': imagePath},
|
||||
);
|
||||
state = state.copyWith(messages: [...state.messages, userMsg]);
|
||||
|
||||
// 异步上传图片
|
||||
String? uploadedUrl;
|
||||
try {
|
||||
final api = ref.read(apiClientProvider);
|
||||
uploadedUrl = await api.uploadFile('/api/upload', file);
|
||||
} catch (_) {
|
||||
// 上传失败:保留本地路径,仍然可以本地显示
|
||||
}
|
||||
|
||||
// 更新消息元数据(上传成功则替换为远程 URL)
|
||||
final finalUrl = uploadedUrl ?? imagePath;
|
||||
final updatedMsgs = state.messages.toList();
|
||||
final idx = updatedMsgs.indexWhere((m) => m.id == userMsg.id);
|
||||
if (idx >= 0) {
|
||||
updatedMsgs[idx] = ChatMessage(
|
||||
id: userMsg.id,
|
||||
role: 'user',
|
||||
content: userMsg.content,
|
||||
createdAt: userMsg.createdAt,
|
||||
metadata: {'imageUrl': finalUrl},
|
||||
);
|
||||
state = state.copyWith(messages: updatedMsgs);
|
||||
}
|
||||
|
||||
// 将图片 URL 作为消息内容发送给 AI
|
||||
final msgWithImage = text.isNotEmpty ? '$text\n[图片已上传]' : '[图片已上传]';
|
||||
await _sendToAI(msgWithImage);
|
||||
}
|
||||
|
||||
Future<void> sendMessage(String text) async {
|
||||
if (text.trim().isEmpty || state.isStreaming) return;
|
||||
|
||||
@@ -168,6 +188,10 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
state = state.copyWith(
|
||||
messages: [...state.messages, userMsg], isStreaming: true);
|
||||
|
||||
await _sendToAI(text);
|
||||
}
|
||||
|
||||
Future<void> _sendToAI(String text) async {
|
||||
final aiMsg = ChatMessage(
|
||||
id: '${DateTime.now().millisecondsSinceEpoch}_ai',
|
||||
role: 'assistant',
|
||||
@@ -175,6 +199,8 @@ class ChatNotifier extends Notifier<ChatState> {
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
state = state.copyWith(isStreaming: true);
|
||||
|
||||
try {
|
||||
final token = await ref.read(apiClientProvider).accessToken;
|
||||
if (token == null) {
|
||||
|
||||
@@ -53,9 +53,37 @@ final medicationReminderProvider = FutureProvider<List<Map<String, dynamic>>>((r
|
||||
/// 医生列表 Provider
|
||||
final doctorListProvider = FutureProvider<List<Map<String, dynamic>>>((ref) async {
|
||||
final service = ref.watch(consultationServiceProvider);
|
||||
return service.getDoctors();
|
||||
try {
|
||||
return await service.getDoctors().timeout(const Duration(seconds: 8));
|
||||
} catch (_) {
|
||||
return _fallbackDoctors;
|
||||
}
|
||||
});
|
||||
|
||||
const _fallbackDoctors = [
|
||||
{
|
||||
'id': 'doc_1',
|
||||
'name': '张医生',
|
||||
'title': '主任医师',
|
||||
'department': '心内科',
|
||||
'introduction': '擅长冠心病、高血压术后管理,20年临床经验',
|
||||
},
|
||||
{
|
||||
'id': 'doc_2',
|
||||
'name': '李医生',
|
||||
'title': '副主任医师',
|
||||
'department': '内分泌科',
|
||||
'introduction': '擅长糖尿病、甲状腺疾病管理,15年临床经验',
|
||||
},
|
||||
{
|
||||
'id': 'doc_3',
|
||||
'name': '王医生',
|
||||
'title': '主治医师',
|
||||
'department': '营养科',
|
||||
'introduction': '擅长术后营养指导、饮食方案制定,10年临床经验',
|
||||
},
|
||||
];
|
||||
|
||||
/// 问诊配额 Provider
|
||||
final consultationQuotaProvider = FutureProvider<Map<String, dynamic>>((ref) async {
|
||||
final service = ref.watch(consultationServiceProvider);
|
||||
@@ -65,5 +93,18 @@ final consultationQuotaProvider = FutureProvider<Map<String, dynamic>>((ref) asy
|
||||
/// 当前运动计划 Provider
|
||||
final currentExercisePlanProvider = FutureProvider<Map<String, dynamic>?>((ref) async {
|
||||
final service = ref.watch(exerciseServiceProvider);
|
||||
return service.getCurrentPlan();
|
||||
try {
|
||||
return await service.getCurrentPlan().timeout(const Duration(seconds: 8));
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
/// 拍照/相册直接触发(无需跳转页面)
|
||||
final cameraActionProvider = NotifierProvider<CameraActionNotifier, String?>(CameraActionNotifier.new);
|
||||
|
||||
class CameraActionNotifier extends Notifier<String?> {
|
||||
@override String? build() => null;
|
||||
void trigger(String action) => state = action;
|
||||
void clear() => state = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user