fix team pro tag
This commit is contained in:
@@ -99,32 +99,42 @@ export function ProjectSelector({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log(`开始获取项目数据,用户ID: ${userId}, 团队ID过滤: ${effectiveTeamIds?.join(', ') || '无'}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('Supabase客户端已创建,准备获取项目数据');
|
||||||
|
|
||||||
if (effectiveTeamIds && effectiveTeamIds.length > 0) {
|
if (effectiveTeamIds && effectiveTeamIds.length > 0) {
|
||||||
// If team IDs are provided, get projects for those teams
|
// If team IDs are provided, get projects for those teams
|
||||||
|
console.log(`通过团队ID获取项目: ${effectiveTeamIds.join(', ')}`);
|
||||||
|
|
||||||
const { data: projectsData, error: projectsError } = await supabase
|
const { data: projectsData, error: projectsError } = await supabase
|
||||||
.from('team_projects')
|
.from('team_projects')
|
||||||
.select('project_id, projects:project_id(*), teams:team_id(name)')
|
.select('project_id, projects:project_id(*), teams:team_id(name)')
|
||||||
.in('team_id', effectiveTeamIds)
|
.in('team_id', effectiveTeamIds)
|
||||||
.is('projects.deleted_at', null);
|
.is('projects.deleted_at', null);
|
||||||
|
|
||||||
|
console.log(`团队项目查询结果:`, projectsData ? `找到${projectsData.length}个` : '无数据',
|
||||||
|
projectsError ? `错误: ${projectsError.message}` : '无错误');
|
||||||
|
|
||||||
if (projectsError) throw projectsError;
|
if (projectsError) throw projectsError;
|
||||||
|
|
||||||
if (!projectsData || projectsData.length === 0) {
|
if (!projectsData || projectsData.length === 0) {
|
||||||
|
console.log('未找到团队项目,返回空列表');
|
||||||
if (isMounted) setProjects([]);
|
if (isMounted) setProjects([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract projects from response with team info
|
// Extract projects from response with team info
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
|
console.log('处理团队项目数据');
|
||||||
const projectList: Project[] = [];
|
const projectList: Project[] = [];
|
||||||
|
|
||||||
for (const item of projectsData as ProjectWithTeam[]) {
|
for (const item of projectsData as unknown as ProjectWithTeam[]) {
|
||||||
if (item.projects && typeof item.projects === 'object' && 'id' in item.projects && 'name' in item.projects) {
|
if (item.projects && typeof item.projects === 'object' && 'id' in item.projects && 'name' in item.projects) {
|
||||||
const project = item.projects as Project;
|
const project = item.projects as Project;
|
||||||
if (item.teams && 'name' in item.teams) {
|
if (item.teams && typeof item.teams === 'object' && 'name' in item.teams) {
|
||||||
project.team_name = item.teams.name;
|
project.team_name = item.teams.name;
|
||||||
}
|
}
|
||||||
// Avoid duplicate projects from different teams
|
// Avoid duplicate projects from different teams
|
||||||
@@ -134,25 +144,33 @@ export function ProjectSelector({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`处理后的项目数据: ${projectList.length}个`);
|
||||||
setProjects(projectList);
|
setProjects(projectList);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If no team IDs, get all user's projects
|
// If no team IDs, get all user's projects
|
||||||
|
console.log(`获取用户所有项目,用户ID: ${userId}`);
|
||||||
|
|
||||||
const { data: projectsData, error: projectsError } = await supabase
|
const { data: projectsData, error: projectsError } = await supabase
|
||||||
.from('user_projects')
|
.from('user_projects')
|
||||||
.select('project_id, projects:project_id(*)')
|
.select('project_id, projects:project_id(*)')
|
||||||
.eq('user_id', userId)
|
.eq('user_id', userId)
|
||||||
.is('projects.deleted_at', null);
|
.is('projects.deleted_at', null);
|
||||||
|
|
||||||
|
console.log(`用户项目查询结果:`, projectsData ? `找到${projectsData.length}个` : '无数据',
|
||||||
|
projectsError ? `错误: ${projectsError.message}` : '无错误');
|
||||||
|
|
||||||
if (projectsError) throw projectsError;
|
if (projectsError) throw projectsError;
|
||||||
|
|
||||||
if (!projectsData || projectsData.length === 0) {
|
if (!projectsData || projectsData.length === 0) {
|
||||||
|
console.log('未找到用户项目,返回空列表');
|
||||||
if (isMounted) setProjects([]);
|
if (isMounted) setProjects([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch team info for these projects
|
// Fetch team info for these projects
|
||||||
const projectIds = projectsData.map(item => item.project_id);
|
const projectIds = projectsData.map(item => item.project_id);
|
||||||
|
console.log(`获取项目的团队信息,项目IDs: ${projectIds.join(', ')}`);
|
||||||
|
|
||||||
// Get team info for each project
|
// Get team info for each project
|
||||||
const { data: teamProjectsData, error: teamProjectsError } = await supabase
|
const { data: teamProjectsData, error: teamProjectsError } = await supabase
|
||||||
@@ -160,6 +178,8 @@ export function ProjectSelector({
|
|||||||
.select('project_id, teams:team_id(name)')
|
.select('project_id, teams:team_id(name)')
|
||||||
.in('project_id', projectIds);
|
.in('project_id', projectIds);
|
||||||
|
|
||||||
|
console.log(`项目团队关系查询结果:`, teamProjectsData ? `找到${teamProjectsData.length}个` : '无数据');
|
||||||
|
|
||||||
if (teamProjectsError) throw teamProjectsError;
|
if (teamProjectsError) throw teamProjectsError;
|
||||||
|
|
||||||
// Create project ID to team name mapping
|
// Create project ID to team name mapping
|
||||||
@@ -174,6 +194,7 @@ export function ProjectSelector({
|
|||||||
|
|
||||||
// Extract projects with team names
|
// Extract projects with team names
|
||||||
if (isMounted && projectsData) {
|
if (isMounted && projectsData) {
|
||||||
|
console.log('处理用户项目数据');
|
||||||
const projectList: Project[] = [];
|
const projectList: Project[] = [];
|
||||||
|
|
||||||
for (const item of projectsData) {
|
for (const item of projectsData) {
|
||||||
@@ -184,12 +205,14 @@ export function ProjectSelector({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`处理后的项目数据: ${projectList.length}个`);
|
||||||
setProjects(projectList);
|
setProjects(projectList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('获取项目数据出错:', err);
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load projects');
|
setError(err instanceof Error ? err.message : '获取项目数据失败');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
@@ -198,9 +221,13 @@ export function ProjectSelector({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取Supabase客户端实例并订阅认证状态变化
|
||||||
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('注册项目选择器认证状态变化监听器');
|
||||||
|
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => {
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => {
|
||||||
|
console.log(`项目选择器认证状态变化: ${event}`, session ? `用户ID: ${session.user.id}` : '无会话');
|
||||||
if (event === 'SIGNED_IN' && session?.user?.id) {
|
if (event === 'SIGNED_IN' && session?.user?.id) {
|
||||||
fetchProjects(session.user.id);
|
fetchProjects(session.user.id);
|
||||||
} else if (event === 'SIGNED_OUT') {
|
} else if (event === 'SIGNED_OUT') {
|
||||||
@@ -209,16 +236,36 @@ export function ProjectSelector({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化时获取当前会话
|
||||||
|
console.log('项目选择器获取当前会话状态');
|
||||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||||
|
console.log('项目选择器当前会话状态:', session ? `用户已登录,ID: ${session.user.id}` : '用户未登录');
|
||||||
if (session?.user?.id) {
|
if (session?.user?.id) {
|
||||||
fetchProjects(session.user.id);
|
fetchProjects(session.user.id);
|
||||||
|
} else {
|
||||||
|
// 如果没有会话但组件需要初始化,可以设置加载完成
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('项目选择器获取会话状态失败:', err);
|
||||||
|
// 确保即使获取会话失败也停止加载状态
|
||||||
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
console.log('ProjectSelector组件卸载,清理订阅');
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
|
} catch (initError) {
|
||||||
|
console.error('初始化ProjectSelector出错:', initError);
|
||||||
|
// 确保在初始化出错时也停止加载状态
|
||||||
|
setLoading(false);
|
||||||
|
setError('初始化失败,请刷新页面重试');
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
}, [effectiveTeamIds]);
|
}, [effectiveTeamIds]);
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
|
|||||||
@@ -123,31 +123,48 @@ export function TagSelector({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log(`开始获取标签数据, 团队ID过滤: ${effectiveTeamIds?.join(', ') || '无'}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('Supabase客户端已创建,准备获取标签数据');
|
||||||
|
|
||||||
let query = supabase.from('tags').select('*').is('deleted_at', null);
|
let query = supabase.from('tags').select('*').is('deleted_at', null);
|
||||||
|
|
||||||
// Filter by team if teamId is provided
|
// Filter by team if teamId is provided
|
||||||
if (effectiveTeamIds) {
|
if (effectiveTeamIds) {
|
||||||
|
console.log(`通过团队ID过滤标签: ${effectiveTeamIds.join(', ')}`);
|
||||||
query = query.in('team_id', effectiveTeamIds);
|
query = query.in('team_id', effectiveTeamIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: tagsData, error: tagsError } = await query;
|
const { data: tagsData, error: tagsError } = await query;
|
||||||
|
|
||||||
|
console.log(`标签查询结果:`, tagsData ? `找到${tagsData.length}个` : '无数据',
|
||||||
|
tagsError ? `错误: ${tagsError.message}` : '无错误');
|
||||||
|
|
||||||
if (tagsError) throw tagsError;
|
if (tagsError) throw tagsError;
|
||||||
|
|
||||||
if (!tagsData || tagsData.length === 0) {
|
if (!tagsData || tagsData.length === 0) {
|
||||||
|
console.log('未找到标签,返回空列表');
|
||||||
if (isMounted) setTags([]);
|
if (isMounted) setTags([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
|
console.log(`设置${tagsData.length}个标签数据`);
|
||||||
setTags(tagsData as Tag[]);
|
setTags(tagsData as Tag[]);
|
||||||
|
|
||||||
|
// 如果已有value但tags刚加载好,重新设置selectedIds
|
||||||
|
if (value && tagsData.length > 0) {
|
||||||
|
const ids = nameToId(value);
|
||||||
|
console.log(`根据名称设置选中的标签ID: ${ids.join(', ')}`);
|
||||||
|
setSelectedIds(ids);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('获取标签数据出错:', err);
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load tags');
|
setError(err instanceof Error ? err.message : '获取标签数据失败');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
@@ -156,9 +173,13 @@ export function TagSelector({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取Supabase客户端实例并订阅认证状态变化
|
||||||
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('注册标签选择器认证状态变化监听器');
|
||||||
|
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent) => {
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent) => {
|
||||||
|
console.log(`标签选择器认证状态变化: ${event}`);
|
||||||
if (event === 'SIGNED_IN') {
|
if (event === 'SIGNED_IN') {
|
||||||
fetchTags();
|
fetchTags();
|
||||||
} else if (event === 'SIGNED_OUT') {
|
} else if (event === 'SIGNED_OUT') {
|
||||||
@@ -167,15 +188,25 @@ export function TagSelector({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
supabase.auth.getSession().then(() => {
|
// 初始化时获取标签数据
|
||||||
|
console.log('标签选择器初始化,获取标签数据');
|
||||||
fetchTags();
|
fetchTags();
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
console.log('TagSelector组件卸载,清理订阅');
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [effectiveTeamIds]);
|
} catch (initError) {
|
||||||
|
console.error('初始化TagSelector出错:', initError);
|
||||||
|
// 确保在初始化出错时也停止加载状态
|
||||||
|
setLoading(false);
|
||||||
|
setError('初始化失败,请刷新页面重试');
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [effectiveTeamIds, nameToId, value]);
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
if (!loading && !error && tags.length > 0) {
|
if (!loading && !error && tags.length > 0) {
|
||||||
|
|||||||
@@ -68,44 +68,98 @@ export function TeamSelector({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log(`开始获取团队数据,用户ID: ${userId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('Supabase客户端已创建,准备获取团队数据');
|
||||||
|
|
||||||
// 尝试创建默认团队和项目(如果用户还没有)
|
// 先尝试直接获取团队数据,不等待create-default
|
||||||
try {
|
try {
|
||||||
const response = await limqRequest('team/create-default', 'POST');
|
|
||||||
console.log('Default team creation response:', response);
|
|
||||||
} catch (teamError) {
|
|
||||||
console.error('Error creating default team:', teamError);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data: memberships, error: membershipError } = await supabase
|
const { data: memberships, error: membershipError } = await supabase
|
||||||
.from('team_membership')
|
.from('team_membership')
|
||||||
.select('team_id')
|
.select('team_id')
|
||||||
.eq('user_id', userId);
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
console.log(`团队成员关系查询结果:`, memberships ? `找到${memberships.length}个` : '无数据', membershipError ? `错误: ${membershipError.message}` : '无错误');
|
||||||
|
|
||||||
if (membershipError) throw membershipError;
|
if (membershipError) throw membershipError;
|
||||||
|
|
||||||
if (!memberships || memberships.length === 0) {
|
if (!memberships || memberships.length === 0) {
|
||||||
if (isMounted) setTeams([]);
|
console.log('未找到团队成员关系,尝试创建默认团队');
|
||||||
|
|
||||||
|
// 尝试创建默认团队和项目(如果用户还没有)
|
||||||
|
try {
|
||||||
|
const response = await limqRequest('team/create-default', 'POST');
|
||||||
|
console.log('默认团队创建成功:', response);
|
||||||
|
|
||||||
|
// 创建默认团队后重新获取团队列表
|
||||||
|
const { data: refreshedMemberships, error: refreshError } = await supabase
|
||||||
|
.from('team_membership')
|
||||||
|
.select('team_id')
|
||||||
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
console.log(`刷新后的团队成员关系:`, refreshedMemberships ? `找到${refreshedMemberships.length}个` : '无数据');
|
||||||
|
|
||||||
|
if (refreshError) throw refreshError;
|
||||||
|
|
||||||
|
if (!refreshedMemberships || refreshedMemberships.length === 0) {
|
||||||
|
if (isMounted) {
|
||||||
|
console.log('创建默认团队后仍未找到团队,设置空团队列表');
|
||||||
|
setTeams([]);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const teamIds = memberships.map(m => m.team_id);
|
const teamIds = refreshedMemberships.map(m => m.team_id);
|
||||||
|
console.log('获取到团队IDs:', teamIds);
|
||||||
|
|
||||||
const { data: teamsData, error: teamsError } = await supabase
|
const { data: teamsData, error: teamsError } = await supabase
|
||||||
.from('teams')
|
.from('teams')
|
||||||
.select('*')
|
.select('*')
|
||||||
.in('id', teamIds)
|
.in('id', teamIds)
|
||||||
.is('deleted_at', null);
|
.is('deleted_at', null);
|
||||||
|
|
||||||
|
console.log(`团队数据查询结果:`, teamsData ? `找到${teamsData.length}个` : '无数据');
|
||||||
|
|
||||||
if (teamsError) throw teamsError;
|
if (teamsError) throw teamsError;
|
||||||
|
|
||||||
if (isMounted && teamsData) {
|
if (isMounted && teamsData) {
|
||||||
setTeams(teamsData);
|
setTeams(teamsData);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
} catch (teamError) {
|
||||||
|
console.error('创建默认团队失败:', teamError);
|
||||||
|
// 创建失败也继续,返回空列表
|
||||||
|
if (isMounted) setTeams([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const teamIds = memberships.map(m => m.team_id);
|
||||||
|
console.log('获取到团队IDs:', teamIds);
|
||||||
|
|
||||||
|
const { data: teamsData, error: teamsError } = await supabase
|
||||||
|
.from('teams')
|
||||||
|
.select('*')
|
||||||
|
.in('id', teamIds)
|
||||||
|
.is('deleted_at', null);
|
||||||
|
|
||||||
|
console.log(`团队数据查询结果:`, teamsData ? `找到${teamsData.length}个` : '无数据');
|
||||||
|
|
||||||
|
if (teamsError) throw teamsError;
|
||||||
|
|
||||||
|
if (isMounted && teamsData) {
|
||||||
|
setTeams(teamsData);
|
||||||
|
}
|
||||||
|
} catch (dataError) {
|
||||||
|
console.error('获取团队数据失败:', dataError);
|
||||||
|
throw dataError;
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('获取团队数据出错:', err);
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to load teams');
|
setError(err instanceof Error ? err.message : '获取团队数据失败');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
@@ -114,9 +168,13 @@ export function TeamSelector({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取Supabase客户端实例并订阅认证状态变化
|
||||||
|
try {
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
|
console.log('注册认证状态变化监听器');
|
||||||
|
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => {
|
const { data: { subscription } } = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => {
|
||||||
|
console.log(`认证状态变化: ${event}`, session ? `用户ID: ${session.user.id}` : '无会话');
|
||||||
if (event === 'SIGNED_IN' && session?.user?.id) {
|
if (event === 'SIGNED_IN' && session?.user?.id) {
|
||||||
fetchTeams(session.user.id);
|
fetchTeams(session.user.id);
|
||||||
} else if (event === 'SIGNED_OUT') {
|
} else if (event === 'SIGNED_OUT') {
|
||||||
@@ -125,16 +183,36 @@ export function TeamSelector({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化时获取当前会话
|
||||||
|
console.log('获取当前会话状态');
|
||||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||||
|
console.log('当前会话状态:', session ? `用户已登录,ID: ${session.user.id}` : '用户未登录');
|
||||||
if (session?.user?.id) {
|
if (session?.user?.id) {
|
||||||
fetchTeams(session.user.id);
|
fetchTeams(session.user.id);
|
||||||
|
} else {
|
||||||
|
// 如果没有会话但组件需要初始化,可以设置加载完成
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('获取会话状态失败:', err);
|
||||||
|
// 确保即使获取会话失败也停止加载状态
|
||||||
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
console.log('TeamSelector组件卸载,清理订阅');
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
|
} catch (initError) {
|
||||||
|
console.error('初始化TeamSelector出错:', initError);
|
||||||
|
// 确保在初始化出错时也停止加载状态
|
||||||
|
setLoading(false);
|
||||||
|
setError('初始化失败,请刷新页面重试');
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import type { Database } from "@/types/supabase";
|
|||||||
|
|
||||||
let supabase: SupabaseClient<Database> | null = null;
|
let supabase: SupabaseClient<Database> | null = null;
|
||||||
|
|
||||||
// 简单的存储适配器,使用localStorage
|
// 增强的存储适配器,使用localStorage并添加更多错误处理
|
||||||
const storageAdapter = {
|
const storageAdapter = {
|
||||||
getItem: async (key: string) => {
|
getItem: async (key: string) => {
|
||||||
try {
|
try {
|
||||||
const item = localStorage.getItem(key);
|
const item = localStorage.getItem(key);
|
||||||
|
console.log(`Storage get for key [${key}]: ${item ? "found" : "not found"}`);
|
||||||
return item;
|
return item;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Storage get error:", error);
|
console.error("Storage get error:", error);
|
||||||
@@ -18,6 +19,7 @@ const storageAdapter = {
|
|||||||
setItem: async (key: string, value: string) => {
|
setItem: async (key: string, value: string) => {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, value);
|
localStorage.setItem(key, value);
|
||||||
|
console.log(`Storage set for key [${key}] successful`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Storage set error:", error);
|
console.error("Storage set error:", error);
|
||||||
}
|
}
|
||||||
@@ -26,18 +28,42 @@ const storageAdapter = {
|
|||||||
removeItem: async (key: string) => {
|
removeItem: async (key: string) => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
|
console.log(`Storage remove for key [${key}] successful`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Storage remove error:", error);
|
console.error("Storage remove error:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 添加一个函数来检查Supabase连接状态
|
||||||
|
export const checkSupabaseConnection = async (): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const client = getSupabaseClient();
|
||||||
|
const { error } = await client.from('_health').select('*').limit(1);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Supabase connection check failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Supabase connection check successful');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Supabase connection check exception:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getSupabaseClient = (): SupabaseClient<Database> => {
|
export const getSupabaseClient = (): SupabaseClient<Database> => {
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
||||||
|
console.error('Missing Supabase environment variables');
|
||||||
throw new Error('Missing Supabase environment variables');
|
throw new Error('Missing Supabase environment variables');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Creating new Supabase client with URL:', process.env.NEXT_PUBLIC_SUPABASE_URL);
|
||||||
|
|
||||||
|
// 使用as断言来避免类型错误
|
||||||
supabase = createClient<Database>(
|
supabase = createClient<Database>(
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
@@ -47,13 +73,27 @@ export const getSupabaseClient = (): SupabaseClient<Database> => {
|
|||||||
storage: storageAdapter,
|
storage: storageAdapter,
|
||||||
persistSession: true,
|
persistSession: true,
|
||||||
autoRefreshToken: true,
|
autoRefreshToken: true,
|
||||||
|
detectSessionInUrl: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
) as SupabaseClient<Database>;
|
||||||
|
|
||||||
|
// 立即检查客户端创建后的会话状态
|
||||||
|
void supabase.auth.getSession().then(({ data: { session } }) => {
|
||||||
|
console.log('Initial session check:', session ? 'Session exists' : 'No session');
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Error checking initial session:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!supabase) {
|
||||||
|
throw new Error('Failed to create Supabase client');
|
||||||
|
}
|
||||||
|
|
||||||
return supabase;
|
return supabase;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearSupabaseInstance = () => {
|
export const clearSupabaseInstance = () => {
|
||||||
|
console.log('Clearing Supabase instance');
|
||||||
supabase = null;
|
supabase = null;
|
||||||
};
|
};
|
||||||
115
lib/api.ts
115
lib/api.ts
@@ -8,23 +8,66 @@ export interface ApiResponse<T = unknown> {
|
|||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common function for authenticated API requests to LIMQ
|
/**
|
||||||
|
* 通用的LIMQ API请求函数,包含重试机制和错误处理
|
||||||
|
*/
|
||||||
export async function limqRequest<T = unknown>(
|
export async function limqRequest<T = unknown>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
||||||
data?: Record<string, unknown>
|
data?: Record<string, unknown>,
|
||||||
|
options?: {
|
||||||
|
retryCount?: number;
|
||||||
|
retryDelay?: number;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
): Promise<ApiResponse<T>> {
|
): Promise<ApiResponse<T>> {
|
||||||
// Get current session
|
// 默认配置
|
||||||
|
const retryCount = options?.retryCount ?? 2; // 默认重试2次
|
||||||
|
const retryDelay = options?.retryDelay ?? 1000; // 默认延迟1秒
|
||||||
|
const timeout = options?.timeout ?? 10000; // 默认超时10秒
|
||||||
|
|
||||||
|
let lastError: Error | null = null;
|
||||||
|
let currentRetry = 0;
|
||||||
|
|
||||||
|
// 创建延迟函数
|
||||||
|
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
// 重试循环
|
||||||
|
while (currentRetry <= retryCount) {
|
||||||
|
try {
|
||||||
|
console.log(`[API] ${method} ${endpoint} 尝试 ${currentRetry + 1}/${retryCount + 1}`);
|
||||||
|
|
||||||
|
// 获取会话
|
||||||
const { data: { session } } = await supabase.auth.getSession();
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
|
|
||||||
|
// 检查会话是否存在
|
||||||
if (!session) {
|
if (!session) {
|
||||||
throw new Error('No active session. User must be authenticated.');
|
console.error(`[API] 未找到活跃会话,用户需要登录`);
|
||||||
|
|
||||||
|
if (currentRetry < retryCount) {
|
||||||
|
currentRetry++;
|
||||||
|
console.log(`[API] 等待 ${retryDelay}ms 后重试获取会话...`);
|
||||||
|
await delay(retryDelay);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_LIMQ_API;
|
return {
|
||||||
const url = `${baseUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
|
success: false,
|
||||||
|
error: '需要登录才能访问API'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const options: RequestInit = {
|
// 获取API基础URL
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_LIMQ_API;
|
||||||
|
if (!baseUrl) {
|
||||||
|
throw new Error('API URL未配置');
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${baseUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;
|
||||||
|
console.log(`[API] 请求URL: ${url}`);
|
||||||
|
|
||||||
|
// 构建请求选项
|
||||||
|
const fetchOptions: RequestInit = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -34,17 +77,63 @@ export async function limqRequest<T = unknown>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (data && (method === 'POST' || method === 'PUT')) {
|
if (data && (method === 'POST' || method === 'PUT')) {
|
||||||
options.body = JSON.stringify(data);
|
fetchOptions.body = JSON.stringify(data);
|
||||||
|
console.log(`[API] 请求数据:`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url, options);
|
// 添加超时控制
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
|
fetchOptions.signal = controller.signal;
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
const response = await fetch(url, fetchOptions);
|
||||||
|
clearTimeout(timeoutId); // 清除超时控制
|
||||||
|
|
||||||
|
// 处理响应
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => null);
|
const errorData = await response.json().catch(() => null);
|
||||||
throw new Error(
|
console.error(`[API] 请求失败: ${response.status} ${response.statusText}`, errorData);
|
||||||
errorData?.error || `Request failed with status ${response.status}`
|
|
||||||
);
|
// 对于认证错误,尝试重试
|
||||||
|
if ((response.status === 401 || response.status === 403) && currentRetry < retryCount) {
|
||||||
|
currentRetry++;
|
||||||
|
console.log(`[API] 认证错误,等待 ${retryDelay}ms 后重试...`);
|
||||||
|
await delay(retryDelay);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
throw new Error(errorData?.error || `请求失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功响应
|
||||||
|
const responseData = await response.json();
|
||||||
|
console.log(`[API] ${method} ${endpoint} 成功`);
|
||||||
|
return responseData;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error instanceof Error ? error : new Error(String(error));
|
||||||
|
console.error(`[API] 请求出错:`, lastError);
|
||||||
|
|
||||||
|
// 对于超时和网络错误,尝试重试
|
||||||
|
if (currentRetry < retryCount &&
|
||||||
|
(error instanceof DOMException && error.name === 'AbortError' ||
|
||||||
|
error instanceof TypeError && error.message.includes('network'))) {
|
||||||
|
currentRetry++;
|
||||||
|
console.log(`[API] 网络错误,等待 ${retryDelay}ms 后重试...`);
|
||||||
|
await delay(retryDelay);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已达到最大重试次数或不是网络错误
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有重试均失败
|
||||||
|
console.error(`[API] ${method} ${endpoint} 失败,已重试 ${currentRetry} 次`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: lastError?.message || '请求失败,请稍后重试'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user