deploy-ease-platform/frontend/src/pages/Dashboard/hooks/useDeploymentData.ts
2025-11-10 16:50:28 +08:00

233 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback, useEffect, useRef } from 'react';
import { useToast } from '@/components/ui/use-toast';
import { getDeployEnvironments } from '../service';
import type { DeployTeam } from '../types';
interface UseDeploymentDataOptions {
onInitialLoadComplete?: () => void;
}
/**
* 部署数据管理 Hook
* 优化点:
* 1. 智能轮询 - 根据部署状态动态调整轮询频率
* 2. 页面可见性检测 - 页面隐藏时暂停轮询
* 3. 状态持久化 - 刷新时保持选中的团队和环境
*/
export function useDeploymentData(options: UseDeploymentDataOptions = {}) {
const { toast } = useToast();
const [loading, setLoading] = useState(true);
const [teams, setTeams] = useState<DeployTeam[]>([]);
const [currentTeamId, setCurrentTeamId] = useState<number | null>(null);
const [currentEnvId, setCurrentEnvId] = useState<number | null>(null);
const [deploying, setDeploying] = useState<Set<number>>(new Set());
const [isInitialLoad, setIsInitialLoad] = useState(true);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const optionsRef = useRef(options);
const toastRef = useRef(toast);
// 同步 options 和 toast 到 ref
useEffect(() => {
optionsRef.current = options;
toastRef.current = toast;
}, [options, toast]);
// 使用 ref 存储当前选中的团队和环境,避免 loadData 依赖变化
const currentSelectionRef = useRef({ teamId: currentTeamId, envId: currentEnvId });
useEffect(() => {
currentSelectionRef.current = { teamId: currentTeamId, envId: currentEnvId };
}, [currentTeamId, currentEnvId]);
// 加载部署环境数据 - 移除状态依赖,使用 ref
const loadData = useCallback(async (showLoading = false) => {
try {
if (showLoading) {
setLoading(true);
}
const response = await getDeployEnvironments();
if (response && response.length > 0) {
const prevTeamId = currentSelectionRef.current.teamId;
const prevEnvId = currentSelectionRef.current.envId;
setTeams(response);
setIsInitialLoad(prev => {
if (prev) {
// 首次加载:默认选中第一个团队和第一个环境
if (response.length > 0) {
setCurrentTeamId(response[0].teamId);
if (response[0].environments.length > 0) {
setCurrentEnvId(response[0].environments[0].environmentId);
}
}
optionsRef.current.onInitialLoadComplete?.();
return false;
} else {
// 后续刷新:保持当前选中的团队和环境
if (prevTeamId !== null) {
const teamExists = response.some(t => t.teamId === prevTeamId);
if (teamExists) {
setCurrentTeamId(prevTeamId);
if (prevEnvId !== null) {
const team = response.find(t => t.teamId === prevTeamId);
const envExists = team?.environments.some(e => e.environmentId === prevEnvId);
if (envExists) {
setCurrentEnvId(prevEnvId);
} else if (team && team.environments.length > 0) {
setCurrentEnvId(team.environments[0].environmentId);
}
}
} else if (response.length > 0) {
setCurrentTeamId(response[0].teamId);
if (response[0].environments.length > 0) {
setCurrentEnvId(response[0].environments[0].environmentId);
}
}
}
// 检查部署状态,如果应用不再是 RUNNING 状态,从 deploying 状态中移除
setDeploying((prevDeploying) => {
const newDeploying = new Set(prevDeploying);
let hasChanges = false;
response.forEach(team => {
team.environments.forEach(env => {
env.applications.forEach(app => {
if (newDeploying.has(app.teamApplicationId)) {
const latestStatus = app.deployStatistics?.latestStatus;
if (latestStatus && latestStatus !== 'RUNNING') {
newDeploying.delete(app.teamApplicationId);
hasChanges = true;
}
}
});
});
});
return hasChanges ? newDeploying : prevDeploying;
});
}
return prev;
});
}
} catch (error) {
console.error('加载数据失败:', error);
setIsInitialLoad(prev => {
if (prev) {
toastRef.current({
variant: 'destructive',
title: '加载失败',
description: '无法加载部署环境数据,请稍后重试',
});
}
return prev;
});
} finally {
if (showLoading) {
setLoading(false);
}
}
}, []);
// 首次加载数据
useEffect(() => {
const initData = async () => {
await loadData(false);
setLoading(false);
};
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 定时刷新数据(智能轮询) - 简化版
useEffect(() => {
// 初始加载时不启动轮询
if (isInitialLoad) return;
// 清除旧的定时器
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
// 页面可见性检测
const handleVisibilityChange = () => {
if (document.hidden) {
// 页面隐藏时停止轮询
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
} else {
// 页面显示时恢复轮询
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
// 智能间隔有部署时5秒无部署时30秒
const interval = deploying.size > 0 ? 5000 : 30000;
intervalRef.current = setInterval(() => {
loadData(false);
}, interval);
}
};
// 启动轮询 - 智能间隔
const interval = deploying.size > 0 ? 5000 : 30000;
intervalRef.current = setInterval(() => {
loadData(false);
}, interval);
// 监听页面可见性变化
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [isInitialLoad, deploying.size, loadData]);
// 切换团队
const handleTeamChange = useCallback((teamId: string) => {
const newTeamId = Number(teamId);
setCurrentTeamId(newTeamId);
const team = teams.find(t => t.teamId === newTeamId);
if (team && team.environments.length > 0) {
setCurrentEnvId(team.environments[0].environmentId);
} else {
setCurrentEnvId(null);
}
}, [teams]);
// 处理部署成功
const handleDeploySuccess = useCallback(async () => {
await loadData(false);
}, [loadData]);
// 获取当前团队和环境
const currentTeam = teams.find(t => t.teamId === currentTeamId);
const currentEnv = currentTeam?.environments.find(e => e.environmentId === currentEnvId);
return {
loading,
teams,
currentTeamId,
currentEnvId,
deploying,
currentTeam,
currentEnv,
setCurrentEnvId,
handleTeamChange,
handleDeploySuccess,
refreshData: () => loadData(false)
};
}