diff --git a/frontend/src/components/console/project/files.tsx b/frontend/src/components/console/project/files.tsx index 66d1a592..83d0914c 100644 --- a/frontend/src/components/console/project/files.tsx +++ b/frontend/src/components/console/project/files.tsx @@ -233,8 +233,12 @@ export const ProjectFileManager = ({ project, onFileSelect, onLoaded, className const [loading, setLoading] = useState(true) const [branches, setBranches] = useState([]) const [selectedBranch, setSelectedBranch] = useState('') + const [branchResolved, setBranchResolved] = useState(false) + const [branchProjectId, setBranchProjectId] = useState('') const projectIdRef = useRef(project?.id) projectIdRef.current = project?.id + const selectedBranchRef = useRef(selectedBranch) + selectedBranchRef.current = selectedBranch const isMountedRef = useRef(true) // 文件内容对话框状态 @@ -253,8 +257,11 @@ export const ProjectFileManager = ({ project, onFileSelect, onLoaded, className setLoading(false) return } + if (!branchResolved) return + if (branchProjectId !== project.id) return const requestedProjectId = project.id + const requestedBranch = selectedBranch setLoading(true) await apiRequest('v1UsersProjectsTreeDetail', { recursive: false, @@ -263,30 +270,44 @@ export const ProjectFileManager = ({ project, onFileSelect, onLoaded, className }, [project?.id], (resp) => { // 忽略过期响应:切换 project 后,旧请求可能晚于新请求返回;或组件已卸载 if (!isMountedRef.current || projectIdRef.current !== requestedProjectId) return + if (selectedBranchRef.current !== requestedBranch) return if (resp.code === 0) { setEntries(sortEntries(resp.data || [])) } else { setEntries([]) } }) + if (!isMountedRef.current || projectIdRef.current !== requestedProjectId) return + if (selectedBranchRef.current !== requestedBranch) return setLoading(false) onLoaded?.() - }, [project?.id, onLoaded, selectedBranch]) + }, [project?.id, onLoaded, selectedBranch, branchProjectId, branchResolved]) // 获取分支列表 useEffect(() => { const fetchBranches = async () => { - if (!project?.git_identity_id || !project?.full_name) { + if (!project?.id || !project?.git_identity_id || !project?.full_name) { setBranches([]) + setSelectedBranch('') + setBranchProjectId(project?.id || '') + setBranchResolved(true) return } + const requestedProjectId = project.id const encodedRepoName = encodeURIComponent(project.full_name) + setBranches([]) + setSelectedBranch('') + setEntries([]) + setLoading(true) + setBranchProjectId('') + setBranchResolved(false) await apiRequest('v1UsersGitIdentitiesBranchesDetail', {}, [project.git_identity_id, encodedRepoName], (resp) => { + if (projectIdRef.current !== requestedProjectId) return if (resp.code === 0 && resp.data) { setBranches(resp.data) - if (resp.data.length > 0 && !selectedBranch) { + if (resp.data.length > 0) { const branchNames = resp.data.map((b: DomainBranch) => b.name || '').filter(Boolean) let defaultBranch = '' if (branchNames.includes('main')) { @@ -299,11 +320,17 @@ export const ProjectFileManager = ({ project, onFileSelect, onLoaded, className setSelectedBranch(defaultBranch) } } + setBranchProjectId(requestedProjectId) + setBranchResolved(true) + }, () => { + if (projectIdRef.current !== requestedProjectId) return + setBranchProjectId(requestedProjectId) + setBranchResolved(true) }) } fetchBranches() - }, [project?.git_identity_id, project?.full_name]) + }, [project?.id, project?.git_identity_id, project?.full_name]) useEffect(() => { isMountedRef.current = true @@ -321,13 +348,15 @@ export const ProjectFileManager = ({ project, onFileSelect, onLoaded, className await apiRequest('v1UsersProjectsTreeBlobDetail', { path: entry.path || '', + ref: selectedBranch || undefined, }, [project?.id], (resp) => { + if (selectedBranchRef.current !== selectedBranch) return if (resp.code === 0 && resp.data?.content) { setFileContent(b64decode(resp.data?.content)) } }) setFileLoading(false) - }, [project?.id]) + }, [project?.id, selectedBranch]) // 处理文件点击 const handleFileClick = useCallback((entry: TreeEntry) => { diff --git a/frontend/src/pages/console/user/project/overview/info-tab.tsx b/frontend/src/pages/console/user/project/overview/info-tab.tsx index 0d68cf0c..3c96e618 100644 --- a/frontend/src/pages/console/user/project/overview/info-tab.tsx +++ b/frontend/src/pages/console/user/project/overview/info-tab.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "react" import { ProjectFileManager } from "@/components/console/project/files" import { Markdown } from "@/components/common/markdown" -import { type DomainProject } from "@/api/Api" +import { type DomainBranch, type DomainProject } from "@/api/Api" import { apiRequest } from "@/utils/requestUtils" import { b64decode } from "@/utils/common" import { @@ -25,7 +25,58 @@ export default function ProjectOverviewInfoTab({ projectId, project }: ProjectOv const [readmeContent, setReadmeContent] = useState("") const [readmePath, setReadmePath] = useState("") + const [readmeRef, setReadmeRef] = useState("") + const [readmeRefProjectId, setReadmeRefProjectId] = useState("") + const [readmeRefResolved, setReadmeRefResolved] = useState(false) const [readmeLoaded, setReadmeLoaded] = useState(false) + const readmeRefValueRef = useRef(readmeRef) + readmeRefValueRef.current = readmeRef + + useEffect(() => { + if (!project?.id || !project?.git_identity_id || !project?.full_name) { + setReadmeRef("") + setReadmeRefProjectId(project?.id || "") + setReadmeRefResolved(true) + return + } + + const requestedProjectId = project.id + const encodedRepoName = encodeURIComponent(project.full_name) + setReadmeRefProjectId("") + setReadmeRefResolved(false) + + apiRequest( + "v1UsersGitIdentitiesBranchesDetail", + {}, + [project.git_identity_id, encodedRepoName], + (resp) => { + if (projectIdRef.current !== requestedProjectId) return + if (resp.code !== 0 || !resp.data?.length) { + setReadmeRef("") + setReadmeRefProjectId(requestedProjectId) + setReadmeRefResolved(true) + return + } + + const branchNames = resp.data.map((b: DomainBranch) => b.name || "").filter(Boolean) + if (branchNames.includes("main")) { + setReadmeRef("main") + } else if (branchNames.includes("master")) { + setReadmeRef("master") + } else { + setReadmeRef(branchNames.sort()[0] || "") + } + setReadmeRefProjectId(requestedProjectId) + setReadmeRefResolved(true) + }, + () => { + if (projectIdRef.current !== requestedProjectId) return + setReadmeRef("") + setReadmeRefProjectId(requestedProjectId) + setReadmeRefResolved(true) + }, + ) + }, [project?.git_identity_id, project?.full_name, project?.id]) useEffect(() => { if (!project?.id) { @@ -34,10 +85,16 @@ export default function ProjectOverviewInfoTab({ projectId, project }: ProjectOv setReadmeLoaded(true) return } + if (!readmeRefResolved) return + if (readmeRefProjectId !== project.id) return + const requestedProjectId = project.id + const requestedReadmeRef = readmeRef setReadmeLoaded(false) - apiRequest("v1UsersProjectsTreeDetail", { recursive: false, path: "" }, [project.id], (resp) => { + const query = { recursive: false, path: "", ref: readmeRef || undefined } + apiRequest("v1UsersProjectsTreeDetail", query, [project.id], (resp) => { if (projectIdRef.current !== requestedProjectId) return + if (readmeRefValueRef.current !== requestedReadmeRef) return if (resp.code !== 0 || !resp.data) { setReadmeLoaded(true) return @@ -52,8 +109,9 @@ export default function ProjectOverviewInfoTab({ projectId, project }: ProjectOv setReadmeLoaded(true) return } - apiRequest("v1UsersProjectsTreeBlobDetail", { path: readmePathVal }, [project.id as string], (r) => { + apiRequest("v1UsersProjectsTreeBlobDetail", { path: readmePathVal, ref: readmeRef || undefined }, [project.id as string], (r) => { if (projectIdRef.current !== requestedProjectId) return + if (readmeRefValueRef.current !== requestedReadmeRef) return if (r.code === 0 && r.data?.content) { setReadmeContent(b64decode(r.data.content)) setReadmePath(readmePathVal) @@ -62,7 +120,7 @@ export default function ProjectOverviewInfoTab({ projectId, project }: ProjectOv setReadmeLoaded(true) }) }) - }, [project?.id]) + }, [project?.id, project?.git_identity_id, project?.full_name, readmeRef, readmeRefProjectId, readmeRefResolved]) const ReadmeHeader = (