diff --git a/client/src/pages/GameDeploymentPage.tsx b/client/src/pages/GameDeploymentPage.tsx index 4e2eb21..f172467 100644 --- a/client/src/pages/GameDeploymentPage.tsx +++ b/client/src/pages/GameDeploymentPage.tsx @@ -284,6 +284,12 @@ const GameDeploymentPage: React.FC = () => { const [cloudInstanceStartCommand, setCloudInstanceStartCommand] = useState('') const [creatingCloudInstance, setCreatingCloudInstance] = useState(false) + // Modrinth 整合包相关状态 + const [modrinthPackId, setModrinthPackId] = useState('') + const [modrinthVersion, setModrinthVersion] = useState('') + const [modrinthCacheList, setModrinthCacheList] = useState([]) + const [modrinthCacheLoading, setModrinthCacheLoading] = useState(false) + // SteamCMD高级选项 const [showAdvanced, setShowAdvanced] = useState(false) const [steamcmdCommand, setSteamcmdCommand] = useState('') @@ -446,8 +452,95 @@ const GameDeploymentPage: React.FC = () => { } } + // 获取 Modrinth 缓存列表 + const fetchModrinthCache = async () => { + try { + setModrinthCacheLoading(true) + const response = await apiClient.getModrinthCache() + if (response.success && response.data) { + setModrinthCacheList(response.data) + } else { + addNotification({ + type: 'error', + title: '获取缓存列表失败', + message: response.message || '无法获取 Modrinth 缓存列表' + }) + } + } catch (error: any) { + console.error('获取 Modrinth 缓存列表失败:', error) + addNotification({ + type: 'error', + title: '获取缓存列表失败', + message: error.message || '网络请求失败' + }) + } finally { + setModrinthCacheLoading(false) + } + } + // 开始云构建 const handleCloudBuild = async () => { + // Modrinth 类型的验证 + if (cloudBuildType === 'modrinth') { + if (!modrinthPackId || !modrinthVersion) { + addNotification({ + type: 'warning', + title: '请填写整合包信息', + message: '请填写整合包 ID/URL 和版本号' + }) + return + } + + if (!cloudBuildPath) { + addNotification({ + type: 'warning', + title: '请选择部署路径', + message: '请先选择整合包部署路径' + }) + return + } + + try { + setBuildingCloud(true) + setCloudBuildLogs(['开始构建 Modrinth 整合包任务...']) + setCloudBuildComplete(false) + setCloudBuildResult(null) + + const response = await apiClient.createCloudBuildTask({ + coreName: modrinthPackId, + version: modrinthVersion, + type: 'modrinth' + }) + + if (response.success && response.data) { + if (response.data.fileName) { + // 缓存命中,使用 fileName 让后端生成下载链接 + setCloudBuildLogs(prev => [...prev, '从缓存获取整合包文件...']) + await handleCloudDownload(response.data.fileName, true) + } else if (response.data.taskId) { + // 需要构建,开始监控进度 + setCloudBuildTask(response.data) + setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在查询状态...']) + monitorCloudBuildProgress(response.data.taskId) + } else { + throw new Error('无效的响应数据') + } + } else { + throw new Error(response.message || '创建构建任务失败') + } + } catch (error: any) { + console.error('Modrinth 整合包构建失败:', error) + addNotification({ + type: 'error', + title: '构建失败', + message: error.message || '创建构建任务失败' + }) + setBuildingCloud(false) + } + return + } + + // msl_Official 类型的验证 if (!selectedCloudCore || !selectedCloudVersion) { addNotification({ type: 'warning', @@ -479,15 +572,17 @@ const GameDeploymentPage: React.FC = () => { }) if (response.success && response.data) { - if (response.data.cached) { - // 缓存命中,直接下载 + if (response.data.fileName) { + // 缓存命中,使用 fileName 让后端生成下载链接 setCloudBuildLogs(prev => [...prev, '从缓存获取服务端文件...']) - await handleCloudDownload(response.data.fileName, null) + await handleCloudDownload(response.data.fileName, true) } else if (response.data.taskId) { - // 需要构建,监控进度 + // 需要构建,开始监控进度 setCloudBuildTask(response.data) - setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在处理...']) + setCloudBuildLogs(prev => [...prev, '构建任务已创建,正在查询状态...']) monitorCloudBuildProgress(response.data.taskId) + } else { + throw new Error('无效的响应数据') } } else { throw new Error(response.message || '创建构建任务失败') @@ -533,8 +628,13 @@ const GameDeploymentPage: React.FC = () => { if (status === 'COMPLETED') { // 停止监控 isMonitoring = false - setCloudBuildLogs(prev => [...prev, '构建完成,开始下载...']) - await handleCloudDownload(null, taskId) + if (downloadUrl) { + setCloudBuildLogs(prev => [...prev, '构建完成,开始下载...']) + // 使用返回的 downloadUrl 直接下载 + await handleCloudDownload(downloadUrl, false) + } else { + throw new Error('任务完成但未返回下载链接') + } } else if (status === 'FAILED') { // 停止监控 isMonitoring = false @@ -542,10 +642,6 @@ const GameDeploymentPage: React.FC = () => { } else if (status === 'PROCESSING' || status === 'QUEUED') { // 继续监控 setTimeout(() => checkProgress(), 2000) - } else { - // 未知状态,停止监控 - isMonitoring = false - console.warn('未知的构建状态:', status) } } } catch (error: any) { @@ -564,15 +660,16 @@ const GameDeploymentPage: React.FC = () => { } // 下载并解压云构建文件 - const handleCloudDownload = async (fileName: string | null, taskId: string | null) => { + const handleCloudDownload = async (downloadUrlOrFileName: string, isFileName: boolean = false) => { try { - setCloudBuildLogs(prev => [...prev, '开始下载服务端文件...']) + const isModrinth = cloudBuildType === 'modrinth' + setCloudBuildLogs(prev => [...prev, `开始下载${isModrinth ? '整合包' : '服务端'}文件...`]) const response = await apiClient.downloadAndExtractCloudBuild({ - fileName: fileName || undefined, - taskId: taskId || undefined, - coreName: selectedCloudCore.name, - version: selectedCloudVersion, + downloadUrl: isFileName ? undefined : downloadUrlOrFileName, + fileName: isFileName ? downloadUrlOrFileName : undefined, + coreName: isModrinth ? modrinthPackId : selectedCloudCore.name, + version: isModrinth ? modrinthVersion : selectedCloudVersion, targetPath: cloudBuildPath }) @@ -588,17 +685,18 @@ const GameDeploymentPage: React.FC = () => { setCloudBuildComplete(true) setCloudBuildResult({ - coreName: selectedCloudCore.name, - version: selectedCloudVersion, + coreName: isModrinth ? modrinthPackId : selectedCloudCore.name, + version: isModrinth ? modrinthVersion : selectedCloudVersion, path: cloudBuildPath, - startCommand + startCommand, + type: cloudBuildType }) setBuildingCloud(false) addNotification({ type: 'success', title: '部署完成', - message: '服务端文件已成功下载并解压到部署目录' + message: `${isModrinth ? '整合包' : '服务端'}文件已成功下载并解压到部署目录` }) } else { throw new Error(response.message || '下载并解压失败') @@ -608,7 +706,7 @@ const GameDeploymentPage: React.FC = () => { addNotification({ type: 'error', title: '下载失败', - message: error.message || '下载服务端文件失败' + message: error.message || `下载${cloudBuildType === 'modrinth' ? '整合包' : '服务端'}文件失败` }) setBuildingCloud(false) } @@ -1760,6 +1858,49 @@ const GameDeploymentPage: React.FC = () => { } }, []) + // 当云构建平台类型或标签页变化时,加载对应数据 + useEffect(() => { + if (activeTab === 'cloud-build') { + if (cloudBuildType === 'modrinth') { + fetchModrinthCache() + } else if (cloudBuildType === 'msl_Official') { + fetchCloudBuildCores() + fetchCloudBuildStats() + } + } + }, [cloudBuildType, activeTab]) + + // 当 Modrinth 整合包 ID 和版本变化时,自动更新路径 + useEffect(() => { + if (cloudBuildType === 'modrinth' && modrinthPackId && modrinthVersion && defaultGamePath) { + // 清理整合包 ID,移除特殊字符和 URL 前缀 + let cleanPackId = modrinthPackId + // 如果是 URL,提取最后的 ID 部分 + if (cleanPackId.includes('/')) { + const parts = cleanPackId.split('/') + cleanPackId = parts[parts.length - 1] + } + // 移除特殊字符 + cleanPackId = cleanPackId.replace(/[<>:"|?*]/g, '').trim() + const cleanVersion = modrinthVersion.replace(/[<>:"|?*]/g, '').trim() + + if (cleanPackId && cleanVersion) { + // 组合文件夹名称 + const folderName = `modrinth-${cleanPackId}-${cleanVersion}` + + // 根据平台使用正确的路径分隔符 + const isWindows = systemInfo?.platform === 'win32' + const separator = isWindows ? '\\' : '/' + + // 确保基础路径以分隔符结尾 + const normalizedBasePath = defaultGamePath.endsWith(separator) || defaultGamePath.endsWith('/') || defaultGamePath.endsWith('\\') + ? defaultGamePath + : defaultGamePath + separator + + setCloudBuildPath(normalizedBasePath + folderName) + } + } + }, [modrinthPackId, modrinthVersion, cloudBuildType, defaultGamePath, systemInfo]) // 自动生成和更新SteamCMD命令 useEffect(() => { @@ -3056,47 +3197,98 @@ const GameDeploymentPage: React.FC = () => { - {/* 核心选择 */} -
- - {cloudBuildCoresLoading ? ( -
- - 加载核心列表... + {/* Modrinth 整合包 ID 输入 */} + {cloudBuildType === 'modrinth' ? ( + <> +
+ + setModrinthPackId(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white" + placeholder="例如: AANobbMI 或 https://modrinth.com/modpack/xxx" + disabled={buildingCloud} + /> +

+ 可以输入整合包 ID 或完整的 Modrinth URL +

- ) : ( - - )} -
+
+ + setModrinthVersion(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white" + placeholder="例如: 5.2.5 或 latest" + disabled={buildingCloud} + /> +

+ 输入版本号 +

+
+ + ) : ( + <> + {/* 核心选择 */} +
+ + {cloudBuildCoresLoading ? ( +
+ + 加载核心列表... +
+ ) : ( + + )} +
+ + )} - {/* 版本选择 */} - {selectedCloudCore && ( + {/* 版本选择 - 仅在 msl_Official 模式下显示 */} + {cloudBuildType === 'msl_Official' && selectedCloudCore && (
)} - {/* 下载统计 */} - {cloudBuildStats && selectedCloudCore && ( + {/* 下载统计 - 仅在 msl_Official 模式下显示 */} + {cloudBuildType === 'msl_Official' && cloudBuildStats && selectedCloudCore && (
{(() => { @@ -3150,6 +3342,102 @@ const GameDeploymentPage: React.FC = () => {
)} + + {/* Modrinth 缓存列表 - 仅在 modrinth 模式下显示 */} + {cloudBuildType === 'modrinth' && ( +
+
+ + +
+ {modrinthCacheLoading ? ( +
+ + 加载缓存列表... +
+ ) : modrinthCacheList.length > 0 ? ( +
+
+ {modrinthCacheList.map((cache, index) => ( + + ))} +
+
+ ) : ( +
+

暂无缓存

+
+ )} +

+ 💡 缓存时间: 48小时 | 点击缓存项可快速填充部署信息 +

+
+ )}
@@ -3179,10 +3467,9 @@ const GameDeploymentPage: React.FC = () => {