- 新增笔记列表页面,支持搜索、筛选和排序 - 新增笔记卡片组件,展示笔记摘要和标签 - 新增笔记详情弹框,支持查看和编辑 - 新增保存到笔记弹框,从 AI 回复快速保存 - 侧边栏添加我的笔记入口 - AI 消息添加保存到笔记按钮
199 lines
5.5 KiB
TypeScript
199 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useCallback } from 'react';
|
|
|
|
// 笔记数据类型
|
|
export interface Note {
|
|
id: number;
|
|
noteId: string;
|
|
userId: string;
|
|
conversationId?: string | null;
|
|
messageId?: string | null;
|
|
title: string;
|
|
content: string;
|
|
tags: string[];
|
|
isPinned: boolean;
|
|
isArchived: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
// 创建笔记参数
|
|
export interface CreateNoteParams {
|
|
title: string;
|
|
content: string;
|
|
tags?: string[];
|
|
conversationId?: string;
|
|
messageId?: string;
|
|
}
|
|
|
|
// 更新笔记参数
|
|
export interface UpdateNoteParams {
|
|
title?: string;
|
|
content?: string;
|
|
tags?: string[];
|
|
isPinned?: boolean;
|
|
isArchived?: boolean;
|
|
}
|
|
|
|
// 筛选参数
|
|
export interface NotesFilter {
|
|
search?: string;
|
|
tags?: string[];
|
|
isPinned?: boolean;
|
|
isArchived?: boolean;
|
|
}
|
|
|
|
/**
|
|
* 笔记管理 Hook
|
|
* 提供笔记的增删改查功能
|
|
*/
|
|
export function useNotes() {
|
|
const [notes, setNotes] = useState<Note[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// 获取笔记列表
|
|
const fetchNotes = useCallback(async (filter?: NotesFilter) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (filter?.search) params.set('search', filter.search);
|
|
if (filter?.tags?.length) params.set('tags', filter.tags.join(','));
|
|
if (filter?.isPinned !== undefined) params.set('isPinned', String(filter.isPinned));
|
|
if (filter?.isArchived !== undefined) params.set('isArchived', String(filter.isArchived));
|
|
|
|
const response = await fetch(`/api/notes?${params.toString()}`);
|
|
if (!response.ok) {
|
|
throw new Error('获取笔记列表失败');
|
|
}
|
|
const data = await response.json();
|
|
setNotes(data.notes || []);
|
|
return data.notes || [];
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '获取笔记列表失败';
|
|
setError(message);
|
|
return [];
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 创建笔记
|
|
const createNote = useCallback(async (params: CreateNoteParams): Promise<Note | null> => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await fetch('/api/notes', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(params),
|
|
});
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || '创建笔记失败');
|
|
}
|
|
const data = await response.json();
|
|
// 将新笔记添加到列表开头
|
|
setNotes(prev => [data.note, ...prev]);
|
|
return data.note;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '创建笔记失败';
|
|
setError(message);
|
|
return null;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 更新笔记
|
|
const updateNote = useCallback(async (noteId: string, params: UpdateNoteParams): Promise<Note | null> => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await fetch(`/api/notes/${noteId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(params),
|
|
});
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || '更新笔记失败');
|
|
}
|
|
const data = await response.json();
|
|
// 更新列表中的笔记
|
|
setNotes(prev => prev.map(note => note.noteId === noteId ? data.note : note));
|
|
return data.note;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '更新笔记失败';
|
|
setError(message);
|
|
return null;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 删除笔记
|
|
const deleteNote = useCallback(async (noteId: string): Promise<boolean> => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await fetch(`/api/notes/${noteId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || '删除笔记失败');
|
|
}
|
|
// 从列表中移除笔记
|
|
setNotes(prev => prev.filter(note => note.noteId !== noteId));
|
|
return true;
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : '删除笔记失败';
|
|
setError(message);
|
|
return false;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 切换置顶状态
|
|
const togglePin = useCallback(async (noteId: string): Promise<boolean> => {
|
|
const note = notes.find(n => n.noteId === noteId);
|
|
if (!note) return false;
|
|
const result = await updateNote(noteId, { isPinned: !note.isPinned });
|
|
return result !== null;
|
|
}, [notes, updateNote]);
|
|
|
|
// 切换归档状态
|
|
const toggleArchive = useCallback(async (noteId: string): Promise<boolean> => {
|
|
const note = notes.find(n => n.noteId === noteId);
|
|
if (!note) return false;
|
|
const result = await updateNote(noteId, { isArchived: !note.isArchived });
|
|
return result !== null;
|
|
}, [notes, updateNote]);
|
|
|
|
// 获取所有标签
|
|
const getAllTags = useCallback((): string[] => {
|
|
const tagSet = new Set<string>();
|
|
notes.forEach(note => {
|
|
note.tags?.forEach(tag => tagSet.add(tag));
|
|
});
|
|
return Array.from(tagSet);
|
|
}, [notes]);
|
|
|
|
return {
|
|
notes,
|
|
loading,
|
|
error,
|
|
fetchNotes,
|
|
createNote,
|
|
updateNote,
|
|
deleteNote,
|
|
togglePin,
|
|
toggleArchive,
|
|
getAllTags,
|
|
};
|
|
}
|