196 lines
5.5 KiB
Markdown
196 lines
5.5 KiB
Markdown
# 全局世界书数据同步问题修复
|
||
|
||
## 🐛 问题描述
|
||
|
||
**现象**: 当世界书文件被删除后,LocalStorage中仍然保存着该世界书的全局状态,导致前端显示一个不存在的"幽灵"世界书。
|
||
|
||
**原因**: `fetchWorldBooks` 函数从LocalStorage加载全局世界书后,没有清理那些已经不存在于后端的世界书。
|
||
|
||
---
|
||
|
||
## ✅ 解决方案
|
||
|
||
### 修复位置
|
||
**文件**: `frontend/src/Store/SideBarLeft/WorldBookSlice.jsx`
|
||
**函数**: `fetchWorldBooks` (第132-165行)
|
||
|
||
### 修复逻辑
|
||
|
||
```javascript
|
||
// 从 LocalStorage 获取全局世界书列表
|
||
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;
|
||
}
|
||
```
|
||
|
||
### 工作流程
|
||
|
||
1. **获取后端数据**: 调用 `GET /api/worldbooks/` 获取所有存在的世界书
|
||
2. **加载LocalStorage**: 从LocalStorage读取全局世界书列表
|
||
3. **对比清理**: 过滤掉LocalStorage中存在但后端不存在的世界书
|
||
4. **更新存储**: 如果发现有被清理的项,更新LocalStorage
|
||
5. **更新State**: 将清理后的列表设置到State中
|
||
|
||
---
|
||
|
||
## 🧪 测试场景
|
||
|
||
### 场景1: 正常情况
|
||
**步骤**:
|
||
1. 创建世界书A和B
|
||
2. 将A和B都设为全局
|
||
3. 刷新页面
|
||
|
||
**预期结果**:
|
||
- ✅ 全局区域显示A和B
|
||
- ✅ LocalStorage中有A和B
|
||
|
||
### 场景2: 文件被删除
|
||
**步骤**:
|
||
1. 创建世界书A和B
|
||
2. 将A和B都设为全局
|
||
3. 手动删除世界书A的文件(或其他方式删除)
|
||
4. 刷新页面
|
||
|
||
**预期结果**:
|
||
- ✅ 全局区域只显示B
|
||
- ✅ LocalStorage中只保留B
|
||
- ✅ 控制台输出: "清理了 1 个不存在的全局世界书"
|
||
|
||
### 场景3: 通过UI删除
|
||
**步骤**:
|
||
1. 创建世界书A和B
|
||
2. 将A和B都设为全局
|
||
3. 在前端UI中删除世界书A
|
||
4. 刷新页面
|
||
|
||
**预期结果**:
|
||
- ✅ 全局区域只显示B
|
||
- ✅ LocalStorage中只保留B
|
||
- ✅ 无错误信息
|
||
|
||
---
|
||
|
||
## 📊 数据流图
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ 页面加载/切换 │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ fetchWorldBooks │
|
||
└────────┬────────┘
|
||
│
|
||
├──► GET /api/worldbooks/ ──► 后端返回现有世界书列表
|
||
│
|
||
├──► loadGlobalWorldBooks() ──► 从LocalStorage读取
|
||
│
|
||
├──► 对比两个列表
|
||
│ ├─ 存在于LocalStorage但不存在于后端 → 清理
|
||
│ └─ 存在于两者 → 保留
|
||
│
|
||
├──► saveGlobalWorldBooks() ──► 更新LocalStorage(如有变化)
|
||
│
|
||
└──► set state ──► 更新UI
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 关键代码说明
|
||
|
||
### 1. 使用Set提高查找效率
|
||
```javascript
|
||
const existingWorldBookNames = new Set(data.map(wb => wb.name));
|
||
```
|
||
- 将后端返回的世界书名称转换为Set
|
||
- Set的查找时间复杂度为O(1),比数组的O(n)更高效
|
||
|
||
### 2. 过滤清理
|
||
```javascript
|
||
const cleanedGlobalBooks = globalBooks.filter(wb =>
|
||
existingWorldBookNames.has(wb.name)
|
||
);
|
||
```
|
||
- 只保留那些在后端也存在的世界书
|
||
- 自动移除"幽灵"世界书
|
||
|
||
### 3. 条件更新
|
||
```javascript
|
||
if (cleanedGlobalBooks.length !== globalBooks.length) {
|
||
console.log(`清理了 ${globalBooks.length - cleanedGlobalBooks.length} 个不存在的全局世界书`);
|
||
saveGlobalWorldBooks(cleanedGlobalBooks);
|
||
globalBooks = cleanedGlobalBooks;
|
||
}
|
||
```
|
||
- 只有在确实有变化时才更新LocalStorage
|
||
- 避免不必要的写入操作
|
||
- 提供调试信息
|
||
|
||
---
|
||
|
||
## ✨ 优势
|
||
|
||
1. **自动清理**: 无需手动干预,自动同步LocalStorage和后端数据
|
||
2. **性能优化**: 使用Set提高查找效率
|
||
3. **用户友好**: 静默清理,只在控制台输出日志
|
||
4. **数据一致性**: 确保LocalStorage中的数据始终与后端保持一致
|
||
5. **无副作用**: 不影响正常的业务流程
|
||
|
||
---
|
||
|
||
## 📝 相关代码位置
|
||
|
||
### LocalStorage操作函数
|
||
```javascript
|
||
// 辅助函数:从 LocalStorage 加载全局世界书
|
||
const loadGlobalWorldBooks = () => {
|
||
try {
|
||
const stored = localStorage.getItem(GLOBAL_WORLDBOOKS_KEY);
|
||
return stored ? JSON.parse(stored) : [];
|
||
} catch (error) {
|
||
console.error('加载全局世界书失败:', error);
|
||
return [];
|
||
}
|
||
};
|
||
|
||
// 辅助函数:保存全局世界书到 LocalStorage
|
||
const saveGlobalWorldBooks = (globalBooks) => {
|
||
try {
|
||
localStorage.setItem(GLOBAL_WORLDBOOKS_KEY, JSON.stringify(globalBooks));
|
||
} catch (error) {
|
||
console.error('保存全局世界书失败:', error);
|
||
}
|
||
};
|
||
```
|
||
|
||
### 删除世界书时的清理
|
||
```javascript
|
||
// deleteWorldBook 函数中已经有清理逻辑
|
||
deleteWorldBook: async (name) => {
|
||
// ...
|
||
const filteredGlobalBooks = state.globalWorldBooks.filter(wb => wb.name !== name);
|
||
saveGlobalWorldBooks(filteredGlobalBooks);
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
✅ **问题已修复**: 全局世界书现在会自动清理不存在的项
|
||
✅ **数据同步**: LocalStorage与后端数据保持一致
|
||
✅ **用户体验**: 不再显示"幽灵"世界书
|
||
✅ **代码健壮**: 增加了数据一致性检查机制
|