完成大量美化
This commit is contained in:
242
frontend/src/Store/SideBarLeft/CharacterSlice.jsx
Normal file
242
frontend/src/Store/SideBarLeft/CharacterSlice.jsx
Normal file
@@ -0,0 +1,242 @@
|
||||
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;
|
||||
@@ -53,6 +53,12 @@ const useWorldBookStore = create((set, get) => ({
|
||||
currentWorldBook: null, // 当前选中的世界书
|
||||
currentEntries: [], // 当前世界书的条目列表
|
||||
currentEntry: null, // 当前选中的条目
|
||||
entriesPagination: { // 条目分页信息
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
total_pages: 0
|
||||
},
|
||||
loading: false, // 加载状态
|
||||
error: null, // 错误信息
|
||||
success: false, // 操作成功状态
|
||||
@@ -116,8 +122,7 @@ const useWorldBookStore = create((set, get) => ({
|
||||
return {
|
||||
loading: false,
|
||||
globalWorldBooks: updatedGlobalBooks,
|
||||
success: true,
|
||||
message: isGlobal ? `已将 "${name}" 设为全局世界书` : `已取消 "${name}" 的全局状态`
|
||||
error: null
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -130,7 +135,18 @@ const useWorldBookStore = create((set, get) => ({
|
||||
const data = await handleResponse(response);
|
||||
|
||||
// 从 LocalStorage 获取全局世界书列表
|
||||
const globalBooks = loadGlobalWorldBooks();
|
||||
let globalBooks = loadGlobalWorldBooks();
|
||||
|
||||
// 清理 LocalStorage 中已不存在的世界书
|
||||
const existingWorldBookNames = new Set(data.map(wb => wb.name));
|
||||
const cleanedGlobalBooks = globalBooks.filter(wb => existingWorldBookNames.has(wb.name));
|
||||
|
||||
// 如果有被清理的项,更新 LocalStorage
|
||||
if (cleanedGlobalBooks.length !== globalBooks.length) {
|
||||
console.log(`清理了 ${globalBooks.length - cleanedGlobalBooks.length} 个不存在的全局世界书`);
|
||||
saveGlobalWorldBooks(cleanedGlobalBooks);
|
||||
globalBooks = cleanedGlobalBooks;
|
||||
}
|
||||
|
||||
set({
|
||||
loading: false,
|
||||
@@ -333,18 +349,24 @@ const useWorldBookStore = create((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
// 异步操作:获取世界书的所有条目
|
||||
fetchWorldBookEntries: async (name) => {
|
||||
// 异步操作:获取世界书的所有条目(支持分页)
|
||||
fetchWorldBookEntries: async (name, page = 1, page_size = 20) => {
|
||||
set({ loading: true, error: null });
|
||||
try {
|
||||
const response = await fetch(`/api/worldbooks/${name}/entries`);
|
||||
const response = await fetch(`/api/worldbooks/${name}/entries?page=${page}&page_size=${page_size}`);
|
||||
const data = await handleResponse(response);
|
||||
|
||||
set(state => {
|
||||
if (state.currentWorldBook?.name === name) {
|
||||
return {
|
||||
loading: false,
|
||||
currentEntries: data,
|
||||
currentEntries: data.entries || [],
|
||||
entriesPagination: {
|
||||
total: data.total || 0,
|
||||
page: data.page || 1,
|
||||
page_size: data.page_size || 20,
|
||||
total_pages: data.total_pages || 0
|
||||
},
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user