完善功能

This commit is contained in:
yxsj245
2025-11-10 19:23:03 +08:00
parent fe1c07d948
commit ef01bee48b
10 changed files with 100 additions and 54 deletions

View File

@@ -18,4 +18,4 @@ const possiblePaths = [
```
14. 涉及交互弹窗的,不要使用浏览器的对话框 使用符合面板风格的弹窗组件
15. 如果需要识别操作系统平台有专门的函数,你需要查找不需要单独写
16. 项目分为前端和后端 浏览器和后端访问的不一定在同一环境和平台,所以请不要使用浏览器侧相关检测等。应当使用后端方法。
16. 编写完毕后需要运行npx tsc --noEmit进行检测

View File

@@ -29,8 +29,9 @@ interface JavaEnvironment {
installStage?: 'download' | 'extract'
}
interface SystemInfo {
interface LocalSystemInfo {
platform: string
rawPlatform?: string // 原始平台标识 (win32/linux/darwin)
arch: string
}
@@ -83,7 +84,7 @@ interface DirectXEnvironment {
}
const EnvironmentManagerPage: React.FC = () => {
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null)
const [systemInfo, setSystemInfo] = useState<LocalSystemInfo | null>(null)
const [javaEnvironments, setJavaEnvironments] = useState<JavaEnvironment[]>([])
const [vcRedistEnvironments, setVcRedistEnvironments] = useState<VcRedistEnvironment[]>([])
const [directxEnvironments, setDirectxEnvironments] = useState<DirectXEnvironment[]>([])
@@ -165,6 +166,7 @@ const EnvironmentManagerPage: React.FC = () => {
if (response.success && response.data) {
setSystemInfo({
platform: response.data.platform,
rawPlatform: response.data.rawPlatform,
arch: response.data.arch
})
}
@@ -1174,7 +1176,11 @@ const EnvironmentManagerPage: React.FC = () => {
// 获取平台图标
const getPlatformIcon = () => {
if (!systemInfo) return <Monitor className="w-4 h-4" />
return systemInfo.platform === 'win32'
// 优先使用 rawPlatform回退到检查 platform 字段
const isWindows = systemInfo?.rawPlatform
? systemInfo.rawPlatform === 'win32'
: systemInfo.platform?.toLowerCase().includes('windows')
return isWindows
? <Monitor className="w-4 h-4" />
: <Server className="w-4 h-4" />
}
@@ -1182,7 +1188,11 @@ const EnvironmentManagerPage: React.FC = () => {
// 获取平台名称
const getPlatformName = () => {
if (!systemInfo) return '未知'
return systemInfo.platform === 'win32' ? 'Windows' : 'Linux'
// 优先使用 rawPlatform回退到检查 platform 字段
const isWindows = systemInfo?.rawPlatform
? systemInfo.rawPlatform === 'win32'
: systemInfo.platform?.toLowerCase().includes('windows')
return isWindows ? 'Windows' : 'Linux'
}
if (loading) {

View File

@@ -52,7 +52,18 @@ interface Games {
[key: string]: GameInfo
}
// 辅助函数:判断是否为 Windows 平台
const isWindowsPlatform = (systemInfo: any): boolean => {
// 优先使用 rawPlatform原始平台标识
if (systemInfo?.rawPlatform) {
return systemInfo.rawPlatform === 'win32'
}
// 回退到检查 platform 字段(友好名称)
if (systemInfo?.platform) {
return systemInfo.platform.toLowerCase().includes('windows')
}
return false
}
const GameDeploymentPage: React.FC = () => {
const { addNotification } = useNotificationStore()
@@ -685,8 +696,9 @@ const GameDeploymentPage: React.FC = () => {
setCloudBuildLogs(prev => [...prev, '部署完成!'])
// 使用后端检测到的启动命令
const startCommand = response.data.startCommand ||
(systemInfo?.platform === 'win32' ? '.\\start.bat' : 'bash start.sh')
// 使用辅助函数判断平台(优先使用 rawPlatform回退到 platform
const startCommand = response.data.startCommand ||
(isWindowsPlatform(systemInfo) ? '.\\start.bat' : 'bash start.sh')
setCloudBuildComplete(true)
setCloudBuildResult({
@@ -724,10 +736,11 @@ const GameDeploymentPage: React.FC = () => {
const instanceName = `${cloudBuildResult.coreName}-${cloudBuildResult.version}`
setCloudInstanceName(instanceName)
setCloudInstanceDescription(`${cloudBuildResult.coreName} ${cloudBuildResult.version} 服务端`)
// 使用后端检测到的启动命令,如果没有则使用默认值
const startCommand = cloudBuildResult.startCommand ||
(systemInfo?.platform === 'win32' ? '.\\start.bat' : 'bash start.sh')
// 使用辅助函数判断平台(优先使用 rawPlatform回退到 platform
const startCommand = cloudBuildResult.startCommand ||
(isWindowsPlatform(systemInfo) ? '.\\start.bat' : 'bash start.sh')
setCloudInstanceStartCommand(startCommand)
setShowCreateCloudInstanceModal(true)
@@ -1654,13 +1667,14 @@ const GameDeploymentPage: React.FC = () => {
}
// 根据服务器类型生成启动命令
const generateStartCommand = (serverType: string, selectedJava: string = 'default', isWindows: boolean = process.platform === 'win32') => {
const generateStartCommand = (serverType: string, selectedJava: string = 'default', isWindows: boolean = isWindowsPlatform(systemInfo)) => {
const lowerServerType = serverType.toLowerCase()
const javaExecutable = getSelectedJavaExecutable(selectedJava)
if (lowerServerType.includes('forge') || lowerServerType.includes('neoforge')) {
// Forge/NeoForge 使用启动脚本
return isWindows ? 'run.bat' : './run.sh'
// 使用辅助函数判断平台(优先使用 rawPlatform回退到 platform
return isWindows ? '.\\run.bat' : 'bash run.sh'
} else if (lowerServerType.includes('fabric') || lowerServerType.includes('quilt')) {
// Fabric/Quilt 重命名为 server.jar
return `${javaExecutable} -jar server.jar`
@@ -1922,11 +1936,12 @@ const GameDeploymentPage: React.FC = () => {
// 监听Java版本选择变化自动更新启动命令
useEffect(() => {
// 更新Minecraft实例启动命令
if (selectedServer && selectedVersion && selectedMinecraftJava) {
// 只在用户手动选择时更新,不覆盖下载后扫描得到的启动命令
if (selectedServer && selectedVersion && selectedMinecraftJava && !downloadResult) {
setInstanceStartCommand(generateStartCommand(selectedServer, selectedMinecraftJava))
setStartCommandEdited(false)
}
}, [selectedMinecraftJava, selectedServer])
}, [selectedMinecraftJava, selectedServer, downloadResult])
useEffect(() => {
// 更新整合包实例启动命令
@@ -1986,7 +2001,8 @@ const GameDeploymentPage: React.FC = () => {
const folderName = `modrinth-${cleanPackId}-${cleanVersion}`
// 根据平台使用正确的路径分隔符
const isWindows = systemInfo?.platform === 'win32'
// 使用辅助函数判断平台(优先使用 rawPlatform回退到 platform
const isWindows = isWindowsPlatform(systemInfo)
const separator = isWindows ? '\\' : '/'
// 确保基础路径以分隔符结尾
@@ -3494,7 +3510,8 @@ const GameDeploymentPage: React.FC = () => {
const folderName = `modrinth-${cleanPackId}-${cleanVersion}`
// 根据平台使用正确的路径分隔符
const isWindows = systemInfo?.platform === 'win32'
// 使用辅助函数判断平台(优先使用 rawPlatform回退到 platform
const isWindows = isWindowsPlatform(systemInfo)
const separator = isWindows ? '\\' : '/'
// 确保基础路径以分隔符结尾
@@ -3966,28 +3983,38 @@ const GameDeploymentPage: React.FC = () => {
</p>
<button
onClick={async () => {
console.log('[Minecraft实例创建] 点击创建实例按钮')
console.log('[Minecraft实例创建] selectedServer:', selectedServer)
console.log('[Minecraft实例创建] downloadResult:', downloadResult)
setInstanceName(`${selectedServer}-${selectedVersion}`)
setInstanceDescription(`Minecraft ${selectedServer} ${selectedVersion} 服务器`)
// 打开弹窗前预扫描并填充启动命令
try {
console.log('[Minecraft实例创建] 打开弹窗前进行目录扫描:', downloadResult.targetDirectory)
const scanResult = await apiClient.scanMinecraftDirectory(downloadResult.targetDirectory)
console.log('[Minecraft实例创建] 扫描结果:', scanResult)
if (scanResult.success && scanResult.data?.recommendedStartCommand) {
let cmd = scanResult.data.recommendedStartCommand as string
console.log('[Minecraft实例创建] 后端推荐命令:', cmd)
if (scanResult.data.startMethod === 'jar_file' && selectedMinecraftJava !== 'default') {
const javaExecutable = getSelectedJavaExecutable(selectedMinecraftJava)
cmd = cmd.replace(/^java\b/, javaExecutable)
}
console.log('[Minecraft实例创建] 最终使用命令:', cmd)
setInstanceStartCommand(cmd)
setStartCommandEdited(false)
} else {
const fallback = generateStartCommand(selectedServer, selectedMinecraftJava)
console.log('[Minecraft实例创建] 使用回退命令:', fallback)
setInstanceStartCommand(fallback)
setStartCommandEdited(false)
}
} catch (e) {
console.error('[Minecraft实例创建] 扫描异常:', e)
const fallback = generateStartCommand(selectedServer, selectedMinecraftJava)
console.log('[Minecraft实例创建] 异常回退命令:', fallback)
setInstanceStartCommand(fallback)
setStartCommandEdited(false)
}

View File

@@ -757,9 +757,13 @@ const InstanceManagerPage: React.FC = () => {
// 检测平台和启动命令格式是否匹配
// 使用后端服务器的平台信息来判断
const serverPlatform = systemInfo?.platform?.toLowerCase() || ''
const isWindowsServer = serverPlatform.includes('win32') || serverPlatform.includes('windows')
const isLinuxServer = serverPlatform.includes('linux') || serverPlatform.includes('darwin')
// 优先使用 rawPlatform回退到检查 platform 字段
const isWindowsServer = systemInfo?.rawPlatform
? systemInfo.rawPlatform === 'win32'
: systemInfo?.platform?.toLowerCase().includes('windows') || false
const isLinuxServer = systemInfo?.rawPlatform
? (systemInfo.rawPlatform === 'linux' || systemInfo.rawPlatform === 'darwin')
: systemInfo?.platform?.toLowerCase().includes('linux') || false
// Windows 平台使用了 Linux 格式 (./)
const isWindowsWithLinuxSlash = startCommand.startsWith('./') && isWindowsServer

View File

@@ -136,6 +136,7 @@ export interface SystemStats {
export interface SystemInfo {
platform: string
rawPlatform?: string // 原始平台标识 (win32/linux/darwin),用于平台判断
arch: string
hostname: string
ipv4: string[]

View File

@@ -96,6 +96,7 @@ export interface ModpackDeployResult {
targetDirectory?: string;
installedMods?: number;
loaderVersion?: string;
serverType?: string; // 服务端类型 (forge/neoforge/fabric/quilt)
}
// ==================== Mrpack处理器类 ====================
@@ -585,13 +586,14 @@ export class MrpackServerAPI {
if (onProgress) {
onProgress('整合包部署完成!', 'success');
}
return {
success: true,
message: '整合包部署成功',
targetDirectory,
installedMods: downloadedMods,
loaderVersion: indexData.dependencies[loaderType] || 'latest'
loaderVersion: indexData.dependencies[loaderType] || 'latest',
serverType: loaderType // 返回服务端类型,用于前端生成启动命令
};
} catch (error) {
// 清理临时文件

View File

@@ -11,6 +11,7 @@ const execAsync = promisify(exec)
interface SystemInfo {
platform: string
rawPlatform: string // 原始平台标识 (win32/linux/darwin)
arch: string
hostname: string
ipv4: string[]
@@ -426,9 +427,10 @@ export class SystemManager extends EventEmitter {
// 获取本地IP地址
const { ipv4, ipv6 } = this.getLocalIpAddresses()
return {
platform: platformName,
rawPlatform: os.platform(), // 保存原始平台标识
arch: os.arch(),
hostname: os.hostname(),
ipv4,

View File

@@ -163,32 +163,27 @@ router.post('/download', authenticateToken, async (req, res) => {
const files = await fs.readdir(targetPath)
let startCommand = ''
const isWindows = process.platform === 'win32'
// 检测是否有run文件
const runFile = files.find(file =>
file.toLowerCase().startsWith('run') &&
(file.endsWith('.bat') || file.endsWith('.sh') || !file.includes('.'))
)
if (runFile) {
// 如果存在run文件优先使用
if (isWindows) {
startCommand = `.\\${runFile}`
} else {
startCommand = runFile.endsWith('.sh') ? `bash ${runFile}` : `./${runFile}`
// 根据平台优先选择对应的启动脚本
if (isWindows) {
// Windows平台优先 run.bat > start.bat
const runBat = files.find(file => file.toLowerCase() === 'run.bat')
const startBat = files.find(file => file.toLowerCase() === 'start.bat')
if (runBat) {
startCommand = `.\\${runBat}`
} else if (startBat) {
startCommand = `.\\${startBat}`
}
} else {
// 检测start文件
const hasStartBat = files.includes('start.bat')
const hasStartSh = files.includes('start.sh')
if (hasStartBat && hasStartSh) {
// 根据当前平台选择
startCommand = isWindows ? '.\\start.bat' : 'bash start.sh'
} else if (hasStartBat) {
startCommand = '.\\start.bat'
} else if (hasStartSh) {
startCommand = 'bash start.sh'
// Linux/Mac平台优先 run.sh > start.sh
const runSh = files.find(file => file.toLowerCase() === 'run.sh')
const startSh = files.find(file => file.toLowerCase() === 'start.sh')
if (runSh) {
startCommand = `bash ${runSh}`
} else if (startSh) {
startCommand = `bash ${startSh}`
}
}

View File

@@ -29,13 +29,15 @@ const packageManager = new LinuxPackageManager()
// 获取系统信息
router.get('/system-info', authenticateToken, async (req, res) => {
try {
const rawPlatform = os.platform()
const systemInfo = {
platform: os.platform(),
platform: rawPlatform, // 保持原样,用于显示
rawPlatform: rawPlatform, // 原始平台标识,用于判断
arch: os.arch(),
type: os.type(),
release: os.release()
}
res.json({
success: true,
data: systemInfo

View File

@@ -877,17 +877,18 @@ router.post('/scan-minecraft-directory', authenticateToken, async (req: Request,
// 优先选择run.bat否则使用第一个找到的.bat文件
const runBat = batFiles.find(f => f.toLowerCase() === 'run.bat')
const scriptFile = runBat || batFiles[0]
recommendedStartCommand = scriptFile
recommendedStartCommand = `.\\${scriptFile}` // 添加 .\ 路径前缀
startMethod = 'bat_script'
logger.info(`[智能检测] 推荐使用BAT脚本: ${scriptFile}`)
logger.info(`[智能检测] 推荐使用BAT脚本: ${recommendedStartCommand}`)
} else if (!isWindows && shFiles.length > 0) {
// Linux/Mac平台优先使用.sh脚本
// 优先选择run.sh否则使用第一个找到的.sh文件
const runSh = shFiles.find(f => f.toLowerCase() === 'run.sh')
const scriptFile = runSh || shFiles[0]
recommendedStartCommand = `./${scriptFile}`
// 使用 bash 命令执行,与云构建保持一致
recommendedStartCommand = `bash ${scriptFile}`
startMethod = 'sh_script'
logger.info(`[智能检测] 推荐使用SH脚本: ${scriptFile}`)
logger.info(`[智能检测] 推荐使用SH脚本: ${recommendedStartCommand}`)
} else if (jarFiles.length > 0) {
// 如果没有对应平台的脚本使用jar文件
// 优先选择server.jar否则使用第一个找到的jar文件
@@ -899,7 +900,9 @@ router.post('/scan-minecraft-directory', authenticateToken, async (req: Request,
} else {
logger.warn(`[智能检测] 未找到任何启动文件 (jar/bat/sh)`)
}
logger.info(`[智能检测] 平台: ${platform}, isWindows: ${isWindows}, 推荐命令: ${recommendedStartCommand}`)
res.json({
success: true,
data: {