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

调试项目列表显示问题

快速定位"接口有数据但不显示"的问题


🎯 问题描述

  • 后端接口返回数据
  • 前端页面不显示项目列表

🔍 诊断步骤

步骤 1:检查浏览器开发者工具

打开浏览器控制台

按 F12 或 右键 → 检查

1.1 查看 Network 请求

  1. 切换到 Network 标签
  2. 过滤 XHR/Fetch 请求
  3. 找到 GET /api/v1/projects 请求
  4. 点击查看详情

检查点

// ✅ 正确的响应格式
{
  "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.folderIdundefined 但期望 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 开发团队