import { create } from 'zustand'; import { persist } from 'zustand/middleware'; const useCharacterStore = create( persist( (set, get) => ({ // 状态 characters: [], // 所有角色卡列表 selectedCharacter: null, // 当前选中的角色 isLoading: false, error: null, currentPage: 1, // 当前页码 pageSize: 12, // 每页显示数量 characterChats: {}, // 存储每个角色的聊天记录 {characterName: [chats]} // 获取所有角色卡 fetchCharacters: async () => { set({ isLoading: true, error: null }); try { const response = await fetch('/api/characters/'); if (!response.ok) throw new Error('Failed to fetch characters'); const data = await response.json(); set({ characters: data, isLoading: false, currentPage: 1 // 重置到第一页 }); } catch (error) { set({ error: error.message, isLoading: false }); } }, // 设置当前页码 setCurrentPage: (page) => { set({ currentPage: page }); }, // 设置每页显示数量 setPageSize: (size) => { set({ pageSize: size, currentPage: 1 }); // 重置到第一页 }, // 获取当前页的角色数据 getCurrentPageCharacters: () => { const { characters, currentPage, pageSize } = get(); const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; return characters.slice(startIndex, endIndex); }, // 获取总页数 getTotalPages: () => { const { characters, pageSize } = get(); return Math.ceil(characters.length / pageSize); }, // 获取角色的聊天记录 fetchCharacterChats: async (characterName) => { const { characterChats } = get(); // 如果已经缓存了,直接返回 if (characterChats[characterName]) { return characterChats[characterName]; } try { const response = await fetch(`/api/characters/${encodeURIComponent(characterName)}/chats`); if (!response.ok) throw new Error('Failed to fetch chats'); const data = await response.json(); // 更新缓存 set((state) => ({ characterChats: { ...state.characterChats, [characterName]: data.chats || [] } })); return data.chats || []; } catch (error) { console.error('获取聊天记录失败:', error); return []; } }, // 选择角色 selectCharacter: (character) => { // 使用函数式更新避免不必要的重渲染 set((state) => { // 如果选中的是同一个角色,不更新状态 if (state.selectedCharacter?.id === character.id) { return state; } return { selectedCharacter: character }; }); }, // 创建角色 createCharacter: async (characterData) => { try { const response = await fetch('/api/characters/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(characterData) }); if (!response.ok) throw new Error('Failed to create character'); const data = await response.json(); // 刷新列表 await get().fetchCharacters(); return data.character; } catch (error) { set({ error: error.message }); throw error; } }, // 更新角色 updateCharacter: async (name, updates) => { try { const response = await fetch(`/api/characters/${encodeURIComponent(name)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); if (!response.ok) throw new Error('Failed to update character'); // 刷新列表 await get().fetchCharacters(); } catch (error) { set({ error: error.message }); throw error; } }, // 删除角色 deleteCharacter: async (name) => { try { const response = await fetch(`/api/characters/${encodeURIComponent(name)}`, { method: 'DELETE' }); if (!response.ok) throw new Error('Failed to delete character'); // 刷新列表 await get().fetchCharacters(); } catch (error) { set({ error: error.message }); throw error; } }, // 上传头像 uploadAvatar: async (name, file) => { try { const formData = new FormData(); formData.append('file', file); const response = await fetch(`/api/characters/${encodeURIComponent(name)}/avatar`, { method: 'POST', body: formData }); if (!response.ok) throw new Error('Failed to upload avatar'); // 刷新列表以更新头像路径 await get().fetchCharacters(); } catch (error) { set({ error: error.message }); throw error; } }, // 导入角色卡 importCharacter: async (file) => { try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/characters/import', { method: 'POST', body: formData }); if (!response.ok) throw new Error('Failed to import character'); const data = await response.json(); // 刷新列表 await get().fetchCharacters(); return data.character; } catch (error) { set({ error: error.message }); throw error; } }, // 导出角色卡为 PNG exportCharacterAsPng: async (name) => { try { const response = await fetch(`/api/characters/${encodeURIComponent(name)}/export/png`, { method: 'POST' }); if (!response.ok) throw new Error('Failed to export character'); // 下载文件 const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${name}.png`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { set({ error: error.message }); throw error; } } }), { name: 'character-storage', partialize: (state) => ({ selectedCharacter: state.selectedCharacter }) } ) ); export default useCharacterStore;