feat(api): 集成后端API,实现数据持久化和用户登录

- 新增token管理和用户信息状态,支持基于token的自动登录
- 实现登录或注册接口调用,自动根据URL参数完成身份验证
- 从后端加载交易记录、消息和待办提醒,替代本地缓存读写
- 对交易记录的增删改操作同步到数据库,确保数据一致性
- 对消息和提醒的增删改操作支持服务器同步,提升数据可靠性
- 优化消息发送逻辑,先保存用户消息,再处理AI回复并存库
- 添加待办提醒切换状态API调用,确保状态同步服务器
- 改进界面互动,支持文件与头像上传功能,更新管家形象
- 采用异步处理并增加错误捕获和用户提示,增强稳定性
This commit is contained in:
2026-01-22 18:25:42 +08:00
parent c06e74bd01
commit 194de6ec06
7 changed files with 1595 additions and 499 deletions

View File

@@ -11,6 +11,8 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
const recognitionRef = useRef<any>(null);
const timerRef = useRef<number | null>(null);
const isStartingRef = useRef(false);
useEffect(() => {
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
@@ -22,30 +24,37 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
recognitionRef.current.onresult = (event: any) => {
const transcript = event.results[0][0].transcript;
if (transcript) onTranscript(transcript);
setIsListening(false);
};
recognitionRef.current.onerror = (event: any) => {
if (event.error === 'network') {
alert("语音识别网络连接失败。请检查网络或尝试切换浏览器(推荐 Chrome/Edge)。");
}
console.error('Speech recognition error', event.error);
setIsListening(false);
};
recognitionRef.current.onend = () => {
setIsListening(false);
isStartingRef.current = false;
};
}
}, [onTranscript]);
const startListening = (e: React.MouseEvent | React.TouchEvent) => {
e.preventDefault();
if (isProcessing || isListening) return;
const startListening = (e: React.PointerEvent) => {
if (e.cancelable) e.preventDefault();
if (isProcessing || isListening || isStartingRef.current) return;
if (recognitionRef.current) {
try {
isStartingRef.current = true;
recognitionRef.current.start();
setIsListening(true);
} catch (e) {
console.error("Failed to start recognition", e);
} catch (err) {
console.error("Failed to start recognition", err);
// 如果 start 失败,立即重置状态,否则会锁死
isStartingRef.current = false;
setIsListening(false);
}
} else {
alert("您的浏览器不支持语音识别,请使用 Chrome 或 Edge。");
@@ -53,20 +62,23 @@ const VoiceInput: React.FC<VoiceInputProps> = ({ onTranscript, isProcessing, isF
};
const stopListening = () => {
// 仅在识别引擎正在运行时尝试停止
// 不要在这里重置 isListening 和 isStartingRef,等待 onend 回调
if (isListening) {
recognitionRef.current?.stop();
setIsListening(false);
try {
recognitionRef.current?.stop();
} catch (err) {
console.error("Failed to stop recognition", err);
}
}
};
return (
<div className={`${isFullWidth ? 'w-full' : ''} relative select-none touch-none`}>
<button
onMouseDown={startListening}
onMouseUp={stopListening}
onMouseLeave={stopListening}
onTouchStart={startListening}
onTouchEnd={stopListening}
onPointerDown={startListening}
onPointerUp={stopListening}
onPointerLeave={stopListening}
disabled={isProcessing}
className={`relative flex items-center justify-center transition-all duration-300 active:scale-95 shadow-lg ${
isFullWidth