You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
12 KiB
12 KiB
调试项目列表显示问题
快速定位"接口有数据但不显示"的问题
🎯 问题描述
- ✅ 后端接口返回数据
- ❌ 前端页面不显示项目列表
🔍 诊断步骤
步骤 1:检查浏览器开发者工具
打开浏览器控制台:
按 F12 或 右键 → 检查
1.1 查看 Network 请求
- 切换到 Network 标签
- 过滤 XHR/Fetch 请求
- 找到
GET /api/v1/projects请求 - 点击查看详情
检查点:
// ✅ 正确的响应格式
{
"success": true,
"code": 200,
"data": {
"items": [
{
"id": "xxx",
"name": "项目名称",
"type": "mine", // ⚠️ 注意:这里是字符串 "mine"
"folderId": null,
// ...
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
1.2 查看 Console 日志
关键日志:
// 搜索这些日志:
📡 API 请求: { method: 'GET', url: '/projects', ... }
🍞 面包屑构建: { currentFolderId: 'xxx', ... }
添加调试日志:
打开 client/src/pages/WorkspacePage.tsx,在第 207 行附近添加:
const filteredProjects = projects.filter((p) => {
console.log('🔍 过滤项目:', {
projectId: p.id,
projectName: p.name,
projectFolderId: p.folderId,
currentFolderId,
isVirtualRoot: isVirtualRoot(currentFolderId),
match: /* 这里是匹配逻辑 */
});
// ...
});
console.log('📊 项目数据统计:', {
totalProjects: projects.length,
filteredProjects: filteredProjects.length,
displayProjects: projs.length,
currentFolderId,
});
步骤 2:常见问题诊断
问题 1:类型不匹配
症状:后端返回字符串 "mine",前端期望数字 1
检查代码:
// client/src/types/project.ts
export const ProjectType = {
MINE: 1, // ⚠️ 前端定义为数字
COLLAB: 2,
} as const;
// server/app/schemas/project.py
class ProjectTypeEnum(str, Enum):
MINE = "mine" # ⚠️ 后端返回字符串
COLLAB = "collab"
解决方案:修改前端类型定义
// client/src/types/project.ts
export type ProjectType = 'mine' | 'collab'; // 改为字符串类型
// 或者修改后端返回整数
问题 2:文件夹过滤逻辑
症状:项目被错误过滤掉了
检查代码:client/src/pages/WorkspacePage.tsx 第 207 行
const filteredProjects = projects.filter((p) => {
// 如果项目没有 folderId,它属于根目录
const projectFolderId = p.folderId || null;
// 当前在虚拟根目录("我的项目" 或 "协作项目")
if (isVirtualRoot(currentFolderId)) {
// 显示没有 folderId 的项目
return projectFolderId === null;
}
// 当前在实际文件夹中
return projectFolderId === currentFolderId;
});
常见错误:
- ❌
p.folderId是undefined但期望null - ❌
currentFolderId是字符串"virtual-mine-root"但比较时出错 - ❌ 项目类型过滤(mine vs collab)
问题 3:空状态判断
症状:虽然有数据,但显示空状态
检查代码:client/src/components/features/project/ProjectGridView.tsx 第 130 行
if (folders.length === 0 && projects.length === 0 && !parentFolderId) {
return <EmptyState ... />;
}
诊断:
console.log('📦 GridView Props:', {
foldersCount: folders.length,
projectsCount: projects.length,
parentFolderId,
shouldShowEmpty: folders.length === 0 && projects.length === 0 && !parentFolderId
});
问题 4:响应拦截器处理
症状:响应被错误处理
检查代码:client/src/services/api/client.ts 第 42 行
apiClient.interceptors.response.use(
(response) => {
const res = response.data;
// 统一响应格式
if (res && typeof res === 'object' && typeof res.success === 'boolean') {
if (res.success) {
return res.data; // ✅ 返回 data 字段
}
// ...
}
// ...
}
);
诊断:
// 在拦截器中添加日志
console.log('📥 API 响应:', {
url: response.config.url,
rawData: response.data,
extractedData: res.data,
});
步骤 3:手动测试数据流
3.1 测试 API 调用
打开浏览器 Console,手动测试:
// 1. 检查 localStorage token
console.log('Token:', localStorage.getItem('token'));
// 2. 手动调用 API
const response = await fetch('http://localhost:8000/api/v1/projects', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const data = await response.json();
console.log('API 响应:', data);
// 3. 检查返回的数据结构
console.log('项目列表:', data.data.items);
console.log('第一个项目:', data.data.items[0]);
3.2 检查 React Query 缓存
// 在浏览器 Console
// 注意:需要先安装 React Query Devtools
// 查看当前缓存
window.__REACT_QUERY_DEVTOOLS__;
或者在组件中添加:
import { useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
console.log('🗄️ React Query 缓存:',
queryClient.getQueryData(['projects', 'list'])
);
🛠️ 快速修复方案
方案 1:添加调试日志
修改文件:client/src/pages/WorkspacePage.tsx
// 在第 55 行附近添加
const { data: projectsData } = useProjects();
console.log('📊 项目数据获取:', {
rawData: projectsData,
items: projectsData?.items,
itemsCount: projectsData?.items?.length,
});
const projects = useMemo(() => {
const result = projectsData?.items || [];
console.log('📦 项目 Memo:', {
count: result.length,
projects: result.map(p => ({ id: p.id, name: p.name, folderId: p.folderId })),
});
return result;
}, [projectsData?.items]);
修改文件:client/src/pages/WorkspacePage.tsx 第 197 行
const { displayFolders, displayProjects } = useMemo(() => {
console.log('🔍 开始过滤:', {
totalProjects: projects.length,
currentFolderId,
isVirtualRoot: isVirtualRoot(currentFolderId),
searchQuery,
});
// ... 过滤逻辑 ...
console.log('✅ 过滤结果:', {
displayFolders: displayFoldersList.length,
displayProjects: projs.length,
});
return { displayFolders: displayFoldersList, displayProjects: projs };
}, [/* deps */]);
方案 2:临时禁用过滤
测试是否是过滤逻辑问题:
// 临时修改,直接显示所有项目
const { displayFolders, displayProjects } = useMemo(() => {
console.log('⚠️ 临时禁用过滤,显示所有项目');
return {
displayFolders: folders,
displayProjects: projects // 直接返回所有项目
};
}, [folders, projects]);
方案 3:检查类型定义
修改文件:client/src/types/project.ts
// 将 ProjectType 改为字符串类型(与后端对齐)
export type ProjectType = 'mine' | 'collab';
// 同时更新 Project 接口
export interface Project {
id: string;
name: string;
type: 'mine' | 'collab'; // ← 改为字符串类型
// ...
}
方案 4:修复响应格式处理
检查文件:client/src/services/api/client.ts
确保响应拦截器正确处理分页数据:
apiClient.interceptors.response.use(
(response) => {
const res = response.data;
console.log('📥 拦截器处理:', {
url: response.config.url,
hasSuccess: 'success' in res,
successValue: res.success,
hasData: 'data' in res,
});
if (res && typeof res === 'object' && typeof res.success === 'boolean') {
if (res.success) {
// 对于项目列表,应该返回完整的 data(包含 items, total 等)
return res.data;
}
// ...
}
// ...
}
);
🎯 快速检查清单
在浏览器 Console 执行以下检查:
// ✅ 检查清单
console.log('=== 诊断信息 ===');
console.log('1. Token:', localStorage.getItem('token') ? '✅ 存在' : '❌ 不存在');
console.log('2. API Base URL:', import.meta.env.VITE_API_BASE_URL);
console.log('3. 当前路径:', window.location.pathname);
console.log('4. 当前文件夹ID:', /* 从 URL 提取 */);
// 手动调用 API
const testApi = async () => {
try {
const res = await fetch('http://localhost:8000/api/v1/projects', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const data = await res.json();
console.log('5. API 响应状态:', res.status === 200 ? '✅ 200' : `❌ ${res.status}`);
console.log('6. 响应格式:', data.success ? '✅ 正确' : '❌ 错误');
console.log('7. 项目数量:', data.data?.items?.length || 0);
console.log('8. 第一个项目:', data.data?.items?.[0]);
} catch (e) {
console.error('❌ API 调用失败:', e);
}
};
testApi();
📝 使用 AI 提示词诊断
调试项目列表显示问题
症状:
- 后端接口返回数据(返回格式:[粘贴响应数据])
- 前端页面不显示项目
浏览器 Console 日志:
[粘贴 Console 输出]
Network 请求详情:
- 请求:[粘贴请求信息]
- 响应:[粘贴响应信息]
请帮我定位问题并提供修复方案。
🚀 一键诊断脚本
复制以下代码到浏览器 Console:
// 🔍 项目列表诊断脚本
(async function diagnose() {
console.clear();
console.log('🔍 开始诊断项目列表显示问题...\n');
const results = {};
// 1. 检查 Token
results.hasToken = !!localStorage.getItem('token');
console.log('1. Token:', results.hasToken ? '✅ 存在' : '❌ 不存在');
// 2. 检查环境变量
results.apiBaseUrl = import.meta.env?.VITE_API_BASE_URL || '未设置';
console.log('2. API Base URL:', results.apiBaseUrl);
// 3. 测试 API 调用
try {
const apiUrl = results.apiBaseUrl.replace('/api/v1', '') + '/api/v1/projects';
const response = await fetch(apiUrl, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
results.apiStatus = response.status;
results.apiData = await response.json();
console.log('3. API 状态:', response.status === 200 ? '✅ 200 OK' : `❌ ${response.status}`);
console.log('4. 响应格式:', results.apiData.success ? '✅ 正确' : '❌ 错误');
console.log('5. 项目数量:', results.apiData.data?.items?.length || 0);
if (results.apiData.data?.items?.length > 0) {
console.log('6. 第一个项目:', results.apiData.data.items[0]);
console.log('\n✅ API 返回数据正常!问题可能在前端数据处理或渲染逻辑。');
console.log('\n📝 建议检查:');
console.log(' - 数据过滤逻辑(WorkspacePage.tsx 第 207 行)');
console.log(' - 类型定义是否匹配(ProjectType: string vs number)');
console.log(' - 空状态判断逻辑(ProjectGridView.tsx 第 130 行)');
} else {
console.log('\n⚠️ API 返回了响应,但没有项目数据。');
console.log('\n📝 建议检查:');
console.log(' - 后端是否有创建项目');
console.log(' - 用户权限是否正确');
console.log(' - 数据库查询逻辑');
}
} catch (error) {
console.error('❌ API 调用失败:', error);
console.log('\n📝 建议检查:');
console.log(' - 后端服务是否运行(http://localhost:8000)');
console.log(' - CORS 配置是否正确');
console.log(' - Token 是否有效');
}
console.log('\n=== 诊断完成 ===');
return results;
})();
最后更新: 2026-02-06
维护者: Jointo 开发团队