项目迁移
Some checks failed
CI/CD / Code Check (push) Has been cancelled
CI/CD / Build Windows (push) Has been cancelled

This commit is contained in:
2026-03-14 21:11:59 +08:00
commit 4654f36202
153 changed files with 55923 additions and 0 deletions

12
front/.env Normal file
View File

@@ -0,0 +1,12 @@
# 前端环境变量配置
# API服务地址
VITE_API_HOST=localhost
VITE_API_PORT=37019
# WebSocket服务地址
VITE_WS_HOST=localhost
VITE_WS_PORT=37019
# 构建模式
NODE_ENV=development

3
front/README.md Normal file
View File

@@ -0,0 +1,3 @@
# 前端
Tauri + Vue + TypeScript

View File

@@ -0,0 +1,152 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// 打包错误处理函数:提供中文解释和原错误输出
function handleBuildError(error, stage) {
let chineseExplanation = '';
const originalError = error?.toString() || JSON.stringify(error);
const stderr = error?.stderr?.toString() || '';
const stdout = error?.stdout?.toString() || '';
if (stderr || stdout) {
const errorOutput = (stderr + stdout).toLowerCase();
if (errorOutput.includes('type') && errorOutput.includes('is not assignable')) {
chineseExplanation = '类型错误:类型不匹配,请检查 TypeScript 类型定义';
} else if (errorOutput.includes('property') && errorOutput.includes('does not exist')) {
chineseExplanation = '类型错误:属性不存在,请检查对象属性名称';
} else if (errorOutput.includes('cannot find module') || errorOutput.includes('module not found')) {
chineseExplanation = '模块错误:找不到模块,请检查依赖是否正确安装';
} else if (errorOutput.includes('enoent') || errorOutput.includes('no such file')) {
chineseExplanation = '文件错误:找不到文件或目录,请检查文件路径';
} else if (errorOutput.includes('eacces') || errorOutput.includes('permission denied')) {
chineseExplanation = '权限错误:权限不足,请以管理员身份运行';
} else if (errorOutput.includes('enoent') && errorOutput.includes('cargo')) {
chineseExplanation = 'Rust环境错误未找到Cargo请确保已安装Rust工具链';
} else if (errorOutput.includes('linker') && errorOutput.includes('not found')) {
chineseExplanation = '链接器错误找不到链接器请安装C++编译工具链';
} else if (errorOutput.includes('failed to resolve') || errorOutput.includes('could not resolve')) {
chineseExplanation = '依赖解析错误:无法解析依赖,请检查网络连接和依赖配置';
} else if (errorOutput.includes('out of memory') || errorOutput.includes('heap')) {
chineseExplanation = '内存错误:内存不足,请关闭其他程序或增加系统内存';
} else if (errorOutput.includes('disk space') || errorOutput.includes('no space')) {
chineseExplanation = '磁盘空间错误:磁盘空间不足,请清理磁盘空间';
} else if (errorOutput.includes('certificate') || errorOutput.includes('ssl')) {
chineseExplanation = '证书错误SSL证书问题请检查系统时间或网络代理设置';
} else if (errorOutput.includes('network') || errorOutput.includes('connection')) {
chineseExplanation = '网络错误:网络连接失败,请检查网络连接和代理设置';
} else if (errorOutput.includes('timeout')) {
chineseExplanation = '超时错误:操作超时,请检查网络或增加超时时间';
} else if (errorOutput.includes('cargo') && errorOutput.includes('failed')) {
chineseExplanation = 'Rust编译错误Rust代码编译失败请检查Rust代码';
} else if (errorOutput.includes('vite') && errorOutput.includes('error')) {
chineseExplanation = 'Vite构建错误前端构建失败请检查Vite配置和源代码';
} else if (errorOutput.includes('tauri') && errorOutput.includes('error')) {
chineseExplanation = 'Tauri打包错误Tauri打包失败请检查Tauri配置';
} else if (errorOutput.includes('nsis') || errorOutput.includes('wix')) {
chineseExplanation = '安装程序错误安装程序生成失败请检查Windows安装工具';
} else if (errorOutput.includes('icon') || errorOutput.includes('logo')) {
chineseExplanation = '图标错误:图标文件问题,请检查图标文件格式和路径';
} else if (errorOutput.includes('bundle') || errorOutput.includes('package')) {
chineseExplanation = '打包错误:应用程序打包失败,请检查打包配置';
} else if (errorOutput.includes('target') && errorOutput.includes('not found')) {
chineseExplanation = '目标平台错误:不支持的目标平台,请检查平台配置';
} else if (errorOutput.includes('version') || errorOutput.includes('semver')) {
chineseExplanation = '版本错误:版本号格式错误,请检查版本号格式';
} else {
chineseExplanation = '未知错误:打包过程中发生未知错误';
}
} else {
chineseExplanation = '未知错误:无法获取错误详情';
}
const errorDetails = {
阶段: stage,
中文解释: chineseExplanation,
原错误: originalError,
完整错误输出: stderr || stdout
};
console.error('\n========================================');
console.error('❌ 打包失败');
console.error('========================================\n');
console.error('📋 错误阶段:', errorDetails.阶段);
console.error('🇨🇳 中文解释:', errorDetails.中文解释);
console.error('🔍 原错误:', errorDetails.原错误);
if (errorDetails.完整错误输出) {
console.error('\n📄 完整错误输出:');
console.error(errorDetails.完整错误输出);
}
console.error('\n========================================\n');
return errorDetails;
}
// 记录构建日志
function logBuild(stage, message) {
const timestamp = new Date().toLocaleString('zh-CN');
const logMessage = `[${timestamp}] [${stage}] ${message}\n`;
const logFile = path.join(__dirname, 'build-error.log');
fs.appendFileSync(logFile, logMessage, 'utf-8');
console.log(logMessage.trim());
}
// 执行命令并处理错误
function executeCommand(command, stage) {
try {
logBuild(stage, `开始执行: ${command}`);
execSync(command, {
stdio: 'inherit',
shell: true
});
logBuild(stage, '✅ 执行成功');
return true;
} catch (error) {
handleBuildError(error, stage);
return false;
}
}
// 主构建流程
async function build() {
console.log('\n========================================');
console.log('🚀 开始打包 DeEarthX V3');
console.log('========================================\n');
// 清理旧的日志
const logFile = path.join(__dirname, 'build-error.log');
if (fs.existsSync(logFile)) {
fs.unlinkSync(logFile);
}
// 阶段1: TypeScript 类型检查
console.log('📦 阶段 1/4: TypeScript 类型检查');
if (!executeCommand('npm run vue-tsc --noEmit', 'TypeScript类型检查')) {
process.exit(1);
}
// 阶段2: Vite 前端构建
console.log('\n📦 阶段 2/4: Vite 前端构建');
if (!executeCommand('npm run vite build', 'Vite前端构建')) {
process.exit(1);
}
// 阶段3: Tauri 应用打包
console.log('\n📦 阶段 3/4: Tauri 应用打包');
if (!executeCommand('npm run tauri build', 'Tauri应用打包')) {
process.exit(1);
}
console.log('\n========================================');
console.log('✅ 打包完成!');
console.error('========================================\n');
}
// 运行构建
build().catch(error => {
handleBuildError(error, '构建流程');
process.exit(1);
});

15
front/index.html Normal file
View File

@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link href="./src/tailwind.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DeEarthX V3</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

257
front/lang/de_de.json Normal file
View File

@@ -0,0 +1,257 @@
{
"common": {
"app_name": "DeEarthX",
"version": "Version",
"status_loading": "Startet",
"status_success": "Normal",
"status_error": "Fehler",
"backend_status": "Backend-Status",
"confirm": "Bestätigen",
"cancel": "Abbrechen",
"submit": "Absenden",
"upload": "Hochladen",
"start": "Starten",
"save": "Speichern",
"delete": "Löschen",
"edit": "Bearbeiten",
"close": "Schließen",
"loading": "Wird geladen...",
"error": "Fehler",
"success": "Erfolg",
"warning": "Warnung",
"info": "Information"
},
"menu": {
"home": "Startseite",
"deearth": "Filter",
"galaxy": "Senden",
"template": "Vorlage",
"setting": "Einstellungen",
"about": "Über"
},
"home": {
"title": "Server-Einrichtung überall und jederzeit!",
"mode_title": "Modus-Auswahl",
"upload_title": "Dateien per Drag & Drop oder Klick hochladen",
"upload_hint": "Bitte verwenden Sie .zip (CurseForge, MCBBS) und .mrpack (Modrinth) Dateien",
"mode_server": "Server-Modus",
"mode_upload": "Upload-Modus",
"preparing": "Wird vorbereitet...",
"task_preparing": "Verbinde mit Backend-Dienst...",
"task_connecting": "Verbindung zum Backend-Dienst erfolgreich, beginne mit Aufgabenverarbeitung...",
"step1_title": "Modpack entpacken",
"step1_desc": "Inhalt entpacken und Dateien herunterladen",
"step2_title": "Mods filtern",
"step2_desc": "Kernfunktion von DeEarthX",
"step3_title": "Server herunterladen",
"step3_desc": "Mod-Loader-Server installieren",
"step4_title": "Abgeschlossen",
"step4_desc": "Alles bereit!",
"progress_title": "Erstellungsfortschritt",
"upload_progress": "Upload-Fortschritt",
"unzip_progress": "Entpack-Fortschritt",
"download_progress": "Download-Fortschritt",
"server_install_progress": "Server-Installation",
"server_install_step": "Schritt",
"server_install_message": "Nachricht",
"server_install_completed": "Installation abgeschlossen!",
"server_install_error": "Installation fehlgeschlagen",
"server_install_duration": "Dauer",
"filter_mods_progress": "Mods filtern",
"filter_mods_total": "Gesamtzahl der Mods",
"filter_mods_current": "Aktuell wird geprüft",
"filter_mods_completed": "Filtern abgeschlossen! {filtered} Client-Mods identifiziert, {moved} verschoben",
"filter_mods_error": "Filtern fehlgeschlagen",
"start_production": "Erstellung starten, Menü nicht wechseln!",
"production_complete": "Server-Erstellung abgeschlossen! Dauer {time} Sekunden!",
"please_select_file": "Bitte zuerst Dateien per Drag & Drop oder Auswahl hochladen",
"only_zip_mrpack": "Nur .zip und .mrpack Dateien können hochgeladen werden",
"file_prepared": "Datei bereit",
"preparing_file": "Datei wird vorbereitet...",
"ws_connecting": "WebSocket-Verbindung wird hergestellt...",
"ws_connected": "WebSocket-Verbindung erfolgreich",
"ws_failed": "WebSocket-Verbindung fehlgeschlagen",
"request_failed": "Anfrage an Backend-Dienst fehlgeschlagen, bitte prüfen Sie, ob der Backend-Dienst läuft",
"backend_error": "DeEarthX.Core hat einen schwerwiegenden Fehler festgestellt!",
"backend_error_desc": "Bitte machen Sie einen Screenshot des gesamten Fensters und senden Sie ihn in die Gruppe\nFehlerinformationen: {error}",
"java_not_found": "Java nicht in Systemvariablen gefunden! Bitte Java installieren, sonst kann der Server-Modus nicht verwendet werden!",
"unknown_step": "Unbekannter Schritt",
"parse_error": "Analyse der Servernachricht fehlgeschlagen",
"speed": "Geschwindigkeit",
"remaining": "Verbleibende Zeit",
"java_error_title": "Java nicht gefunden",
"java_error_desc": "Java nicht in Systemvariablen gefunden! Bitte Java installieren, sonst kann der Server-Modus nicht verwendet werden!\n\nVorschläge:\n1. Java 17 oder höher herunterladen und installieren\n2. Java-Umgebungsvariablen konfigurieren\n3. Anwendung neu starten",
"network_error_title": "Netzwerkfehler",
"network_error_desc": "Netzwerkverbindungsproblem\nFehlerinformationen: {error}",
"file_error_title": "Dateifehler",
"file_error_desc": "Dateioperationsproblem\nFehlerinformationen: {error}",
"memory_error_title": "Speicherfehler",
"memory_error_desc": "Unzureichender Speicher\nFehlerinformationen: {error}",
"unknown_error_title": "Unbekannter Fehler",
"unknown_error_desc": "Ein unbekannter Fehler ist aufgetreten, bitte wenden Sie sich an den technischen Support",
"ws_error_title": "WebSocket-Verbindung fehlgeschlagen",
"ws_error_desc": "Keine WebSocket-Verbindung zum Backend herstellbar",
"suggestions": "Vorgeschlagene Lösungen",
"suggestion_check_network": "Prüfen, ob die Netzwerkverbindung normal ist",
"suggestion_check_firewall": "Firewall-Einstellungen prüfen",
"suggestion_retry": "Später erneut versuchen",
"suggestion_check_disk_space": "Prüfen, ob genügend Speicherplatz vorhanden ist",
"suggestion_check_permission": "Dateiberechtigungseinstellungen prüfen",
"suggestion_check_file_format": "Bestätigen, dass das Dateiformat korrekt ist",
"suggestion_increase_memory": "Der Anwendung mehr Speicher zuweisen",
"suggestion_close_other_apps": "Andere Anwendungen schließen, die Speicher verwenden",
"suggestion_restart_application": "Anwendung neu starten",
"suggestion_check_backend": "Prüfen, ob der Backend-Dienst normal ausgeführt wird",
"suggestion_check_logs": "Protokolldateien für weitere Informationen prüfen",
"suggestion_check_port": "Prüfen, ob Port 37019 belegt ist",
"suggestion_contact_support": "Technischen Support für Hilfe kontaktieren",
"template_select_title": "Server-Vorlage auswählen",
"template_select_desc": "Wählen Sie eine Vorlage für Ihren Server. Die Verwendung einer Vorlage überspringt die Server-Installation und kopiert die Vorlagendateien direkt in das Serververzeichnis",
"template_official_loader": "Keine",
"template_official_loader_desc": "Keine Vorlage verwenden, Standard-Server-Installation durchführen",
"template_name": "Vorlagenname",
"template_description": "Beschreibung",
"template_author": "Autor",
"template_version": "Version",
"template_selected": "Ausgewählte Vorlage",
"template_select_button": "Vorlage auswählen",
"template_loading": "Vorlagenliste wird geladen...",
"template_load_failed": "Laden der Vorlagenliste fehlgeschlagen",
"template_apply_success": "Vorlage erfolgreich angewendet",
"template_apply_failed": "Anwenden der Vorlage fehlgeschlagen"
},
"template": {
"title": "Vorlagenverwaltung",
"description": "Verwalten Sie Ihre Server-Vorlagen, erstellen, anzeigen und löschen Sie Vorlagen",
"create_button": "Vorlage erstellen",
"create_title": "Neue Vorlage erstellen",
"name": "Vorlagenname",
"name_required": "Vorlagenname ist erforderlich",
"name_placeholder": "Bitte Vorlagenname eingeben",
"version": "Version",
"version_placeholder": "Bitte Versionsnummer eingeben",
"description": "Beschreibung",
"description_placeholder": "Bitte Vorlagenbeschreibung eingeben",
"author": "Autor",
"author_placeholder": "Bitte Autorenname eingeben",
"delete_button": "Löschen",
"delete_title": "Vorlage löschen",
"delete_confirm": "Möchten Sie die Vorlage \"{name}\" wirklich löschen?",
"delete_warning": "Dieser Vorgang löscht die Vorlage und alle ihre Dateien dauerhaft und kann nicht rückgängig gemacht werden.",
"delete_success": "Vorlage erfolgreich gelöscht",
"delete_failed": "Löschen der Vorlage fehlgeschlagen",
"create_success": "Vorlage erfolgreich erstellt",
"create_failed": "Erstellen der Vorlage fehlgeschlagen",
"open_folder": "Ordner öffnen",
"open_folder_success": "Vorlagenordner geöffnet",
"open_folder_failed": "Ordner öffnen fehlgeschlagen",
"edit_button": "Bearbeiten",
"edit_title": "Vorlage bearbeiten",
"update_success": "Vorlage erfolgreich aktualisiert",
"update_failed": "Aktualisieren der Vorlage fehlgeschlagen",
"empty": "Noch keine Vorlagen",
"empty_hint": "Klicken Sie auf die Schaltfläche oben, um Ihre erste Vorlage zu erstellen"
},
"setting": {
"title": "DeEarthX Einstellungen",
"subtitle": "Machen Sie DeEarthX V3 noch besser für Sie!",
"category_filter": "Mod-Filter-Einstellungen",
"category_mirror": "Download-Quellen-Einstellungen",
"category_system": "Systemverwaltungseinstellungen",
"filter_hashes_name": "Hash-Filterung",
"filter_hashes_desc": "Unnötige Client-Mods filtern (Hash-Filter-Methode)",
"filter_dexpub_name": "Galaxy Square-Filterung",
"filter_dexpub_desc": "Client-Dateien filtern, die auf der Galaxy Square-Plattform erfasst wurden",
"filter_modrinth_name": "Modrinth-API-Filterung",
"filter_modrinth_desc": "Client/Server-Kompatibilität von Mods über Modrinth-API prüfen",
"filter_mixins_name": "Mixin-Filterung",
"filter_mixins_desc": "Client Mixin-bezogene Dateien filtern",
"mirror_mcimirror_name": "MCIM-Spiegelquelle",
"mirror_mcimirror_desc": "MCIM-Spiegelquelle zum Beschleunigen von Downloads verwenden",
"mirror_bmclapi_name": "BMCLAPI-Spiegelquelle",
"mirror_bmclapi_desc": "BMCLAPI-Spiegelquelle zum Beschleunigen von Downloads verwenden",
"system_oaf_name": "Verzeichnis nach Vorgang öffnen",
"system_oaf_desc": "Verzeichnis automatisch nach Server-Erstellung öffnen",
"system_autozip_name": "Automatisch als zip packen",
"system_autozip_desc": "Automatisch als zip packen nach Server-Erstellung (nicht packen im Server-Modus)",
"switch_on": "Ein",
"switch_off": "Aus",
"language_title": "Spracheinstellungen",
"language_desc": "Oberflächensprache auswählen",
"language_chinese": "Vereinfachtes Chinesisch",
"language_english": "English",
"language_japanese": "日本語",
"language_french": "Français",
"language_german": "Deutsch",
"language_spanish": "Español",
"config_saved": "Konfiguration gespeichert",
"config_load_failed": "Laden der Konfiguration fehlgeschlagen",
"config_save_failed": "Speichern der Konfiguration fehlgeschlagen",
"export_config": "Konfiguration exportieren",
"import_config": "Konfiguration importieren",
"config_exported": "Konfiguration erfolgreich exportiert",
"config_export_failed": "Export der Konfiguration fehlgeschlagen",
"config_imported": "Konfiguration erfolgreich importiert",
"config_import_failed": "Import der Konfiguration fehlgeschlagen",
"config_invalid_format": "Ungültiges Konfigurationsdateiformat"
},
"about": {
"title": "Über DeEarthX",
"subtitle": "Professionelles Tool zur Erstellung von Minecraft-Modpack-Servern",
"about_software": "Über die Software",
"current_version": "Aktuelle Version:",
"build_time": "Build-Zeit:",
"author": "Autor:",
"development_team": "Entwicklungsteam",
"author_tianpao": "天跑",
"contribution_author": "Autor",
"dev2_xcc": "XCC",
"contribution_dev2": "2. Entwickler (Verbesserungen und Optimierungen)",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPI-Spiegel",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIM-Spiegel",
"sponsor": "Sponsoren",
"sponsor_elfidc": "亿讯云",
"sponsor_type_gold": "Gold-Sponsor",
"version_file_read_failed": "Lesen der Versionsdatei fehlgeschlagen"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "Lassen Sie alle Mods hier glänzen",
"mod_submit_title": "Mod-Einreichung",
"mod_type_label": "Mod-Typ",
"mod_type_client": "Client-Mod",
"mod_type_server": "Server-Mod",
"modid_label": "Modid",
"modid_placeholder": "Geben Sie ein Modid ein (mehrere durch Kommas getrennt) oder laden Sie eine Datei hoch, um automatisch zu erhalten",
"modid_count": "{count} Modids aktuell hinzugefügt",
"upload_file_label": "Datei hochladen",
"upload_file_hint": "Klicken oder Dateien hierher ziehen zum Hochladen",
"upload_file_support": "Unterstützt .jar-Dateien, Mehrfachauswahl möglich",
"file_selected": "{count} Dateien ausgewählt",
"start_upload": "Upload starten",
"uploading": "Wird hochgeladen...",
"submit": "{type}-Mods senden",
"submitting": "Wird gesendet...",
"submit_confirm_title": "Senden bestätigen",
"submit_confirm_content": "Möchten Sie wirklich {count} {type}-Mods senden?",
"please_select_file": "Bitte zuerst Dateien auswählen",
"upload_success": "{count} Dateien erfolgreich hochgeladen",
"data_format_error": "Fehler im zurückgegebenen Datenformat",
"upload_failed": "Upload fehlgeschlagen",
"upload_error": "Upload-Fehler, bitte erneut versuchen",
"submit_success": "{type}-Mods erfolgreich gesendet",
"submit_failed": "Senden fehlgeschlagen",
"submit_error": "Sendefehler, bitte erneut versuchen"
},
"message": {
"backend_running": "DeEarthX.Core läuft bereits",
"backend_started": "DeEarthX.Core erfolgreich gestartet",
"backend_port_occupied": "Port 37019 wird von einer anderen Anwendung belegt!",
"backend_start_failed": "DeEarthX.Core konnte nicht gestartet werden, prüfen Sie, ob Port 37019 belegt ist! ({count} Wiederholungsversuche)",
"backend_restart": "DeEarthX.Core wird neu gestartet!",
"retry_start": "Start fehlgeschlagen, Wiederholung ({current}/{max})...",
"config_load_error": "Laden der Konfiguration fehlgeschlagen"
}
}

271
front/lang/en_us.json Normal file
View File

@@ -0,0 +1,271 @@
{
"common": {
"app_name": "DeEarthX",
"version": "Version",
"status_loading": "Starting",
"status_success": "Normal",
"status_error": "Error",
"backend_status": "Backend Status",
"confirm": "Confirm",
"cancel": "Cancel",
"submit": "Submit",
"upload": "Upload",
"start": "Start",
"save": "Save",
"delete": "Delete",
"edit": "Edit",
"close": "Close",
"loading": "Loading...",
"error": "Error",
"success": "Success",
"warning": "Warning",
"info": "Info"
},
"menu": {
"home": "Home",
"deearth": "Filter",
"galaxy": "Submit",
"template": "Template",
"setting": "Settings",
"about": "About"
},
"home": {
"title": "Make server hosting available anytime, anywhere!",
"mode_title": "Mode Selection",
"upload_title": "Drag and drop files here or click to upload",
"upload_hint": "Supports .zip (CurseForge, MCBBS) and .mrpack (Modrinth) files",
"mode_server": "Server Mode",
"mode_upload": "Upload Mode",
"preparing": "Preparing...",
"task_preparing": "Connecting to backend service...",
"task_connecting": "Backend service connected successfully, starting to process task...",
"step1_title": "Extract Modpack",
"step1_desc": "Extract content and download files",
"step2_title": "Filter Mods",
"step2_desc": "Core feature of DeEarthX",
"step3_title": "Download Server",
"step3_desc": "Install mod loader server",
"step4_title": "Complete",
"step4_desc": "Everything is ready!",
"progress_title": "Production Progress",
"upload_progress": "Upload Progress",
"unzip_progress": "Extraction Progress",
"download_progress": "Download Progress",
"server_install_progress": "Server Installation",
"server_install_step": "Step",
"server_install_message": "Message",
"server_install_completed": "Installation completed!",
"server_install_error": "Installation failed",
"server_install_duration": "Duration",
"filter_mods_progress": "Filtering Mods",
"filter_mods_total": "Total Mods",
"filter_mods_current": "Currently Checking",
"filter_mods_completed": "Filtering completed! Identified {filtered} client mods, moved {moved}",
"filter_mods_error": "Filtering failed",
"start_production": "Starting production, please do not switch menus!",
"production_complete": "Server production completed! Total time: {time} seconds!",
"please_select_file": "Please drag and drop or select a file first",
"only_zip_mrpack": "Only .zip and .mrpack files are supported",
"file_prepared": "File preparation completed",
"preparing_file": "Preparing file...",
"ws_connecting": "Establishing WebSocket connection...",
"ws_connected": "WebSocket connected successfully",
"ws_failed": "WebSocket connection failed",
"request_failed": "Failed to request backend service, please check if backend service is running",
"backend_error": "DeEarthX.Core encountered a fatal error!",
"backend_error_desc": "Please take a screenshot of the entire window and send it to the group\nError: {error}",
"java_not_found": "Java not found in system variables! Please install Java, otherwise server mode will be unavailable!",
"unknown_step": "Unknown step",
"parse_error": "Failed to parse server message",
"speed": "Speed",
"remaining": "Remaining",
"java_error_title": "Java Not Found",
"java_error_desc": "Java not found in system variables! Please install Java, otherwise server mode will be unavailable!\n\nSuggestions:\n1. Download and install Java 17 or higher\n2. Configure Java environment variables\n3. Restart the application",
"network_error_title": "Network Error",
"network_error_desc": "Network connection issue\nError: {error}",
"file_error_title": "File Error",
"file_error_desc": "File operation issue\nError: {error}",
"memory_error_title": "Memory Error",
"memory_error_desc": "Insufficient memory\nError: {error}",
"unknown_error_title": "Unknown Error",
"unknown_error_desc": "An unknown error occurred, please contact technical support",
"ws_error_title": "WebSocket Connection Failed",
"ws_error_desc": "Unable to establish WebSocket connection with backend",
"suggestions": "Suggested Solutions",
"suggestion_check_network": "Check if network connection is normal",
"suggestion_check_firewall": "Check firewall settings",
"suggestion_retry": "Try again later",
"suggestion_check_disk_space": "Check if disk space is sufficient",
"suggestion_check_permission": "Check file permission settings",
"suggestion_check_file_format": "Confirm file format is correct",
"suggestion_increase_memory": "Increase memory allocated to the application",
"suggestion_close_other_apps": "Close other applications that use memory",
"suggestion_restart_application": "Restart the application",
"suggestion_check_backend": "Check if backend service is running normally",
"suggestion_check_logs": "Check log files for more information",
"suggestion_check_port": "Check if port 37019 is occupied",
"suggestion_contact_support": "Contact technical support for help",
"template_select_title": "Select Server Template",
"template_select_desc": "Choose a template for your server. Using a template will skip server installation and directly copy template files to the server directory",
"template_official_loader": "None",
"template_official_loader_desc": "Do not use any template, perform standard server installation",
"template_name": "Template Name",
"template_description": "Description",
"template_author": "Author",
"template_version": "Version",
"template_selected": "Selected Template",
"template_select_button": "Select Template",
"template_loading": "Loading template list...",
"template_load_failed": "Failed to load template list",
"template_apply_success": "Template applied successfully",
"template_apply_failed": "Failed to apply template",
"template_import_title": "Import Template",
"template_import_hint": "Drag or click to upload template zip file",
"template_import_success": "Template imported successfully",
"template_import_failed": "Failed to import template",
"template_export_button": "Export",
"template_export_success": "Template exported successfully",
"template_export_failed": "Failed to export template",
"template_export_progress": "Exporting template...",
"template_import_progress": "Importing template...",
"template_download_progress": "Downloading template..."
},
"template": {
"title": "Template Management",
"description": "Manage your server templates, create, view and delete templates",
"create_button": "Create Template",
"create_title": "Create New Template",
"name": "Template Name",
"name_required": "Template name is required",
"name_placeholder": "Please enter template name",
"version": "Version",
"version_placeholder": "Please enter version number",
"description": "Description",
"description_placeholder": "Please enter template description",
"author": "Author",
"author_placeholder": "Please enter author name",
"delete_button": "Delete",
"delete_title": "Delete Template",
"delete_confirm": "Are you sure you want to delete template \"{name}\"?",
"delete_warning": "This operation will permanently delete this template and all its files, cannot be recovered.",
"delete_success": "Template deleted successfully",
"delete_failed": "Failed to delete template",
"create_success": "Template created successfully",
"create_failed": "Failed to create template",
"open_folder": "Open Folder",
"open_folder_success": "Template folder opened",
"open_folder_failed": "Failed to open folder",
"edit_button": "Edit",
"edit_title": "Edit Template",
"update_success": "Template updated successfully",
"update_failed": "Failed to update template",
"empty": "No templates yet",
"empty_hint": "Click the button above to create your first template",
"local_templates": "Local Templates",
"template_store": "Template Store",
"store_empty": "Template store is empty",
"store_empty_hint": "No templates available at the moment",
"install_button": "Install",
"install_success": "Template installed successfully",
"install_failed": "Failed to install template",
"store_load_failed": "Failed to load template store"
},
"setting": {
"title": "DeEarthX Settings",
"subtitle": "Make your DeEarthX V3 suit you better!",
"category_filter": "Mod Filter Settings",
"category_mirror": "Download Source Settings",
"category_system": "System Management Settings",
"filter_hashes_name": "Hash Filter",
"filter_hashes_desc": "Filter unnecessary client mods (hash filtering method)",
"filter_dexpub_name": "Galaxy Square Filter",
"filter_dexpub_desc": "Filter client files recorded in Galaxy Square platform",
"filter_modrinth_name": "Modrinth API Filter",
"filter_modrinth_desc": "Check mod client/server compatibility through Modrinth API",
"filter_mixins_name": "Mixin Filter",
"filter_mixins_desc": "Filter Client Mixin related files",
"mirror_mcimirror_name": "MCIM Mirror",
"mirror_mcimirror_desc": "Use MCIM mirror to accelerate downloads",
"mirror_bmclapi_name": "BMCLAPI Mirror",
"mirror_bmclapi_desc": "Use BMCLAPI mirror to accelerate downloads",
"system_oaf_name": "Open directory after operation",
"system_oaf_desc": "Automatically open directory after server production is completed",
"system_autozip_name": "Auto package to zip",
"system_autozip_desc": "Automatically package to zip after server production is completed (not packaged in server mode)",
"switch_on": "On",
"switch_off": "Off",
"language_title": "Language Settings",
"language_desc": "Select interface display language",
"language_chinese": "简体中文",
"language_english": "English",
"config_saved": "Configuration saved",
"config_load_failed": "Failed to load configuration",
"config_save_failed": "Failed to save configuration",
"export_config": "Export Config",
"import_config": "Import Config",
"config_exported": "Configuration exported successfully",
"config_export_failed": "Failed to export configuration",
"config_imported": "Configuration imported successfully",
"config_import_failed": "Failed to import configuration",
"config_invalid_format": "Invalid configuration file format"
},
"about": {
"title": "About DeEarthX",
"subtitle": "Professional Minecraft modpack server production tool",
"about_software": "About Software",
"current_version": "Current version: ",
"build_time": "Build time: ",
"author": "Author: ",
"development_team": "Development Team",
"author_tianpao": "Tianpao",
"contribution_author": "Author",
"dev2_xcc": "XCC",
"contribution_dev2": "Developer 2 (Improvement and Optimization)",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPI Mirror",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIM Mirror",
"sponsor": "Sponsors",
"sponsor_elfidc": "ElfIDC",
"sponsor_type_gold": "Gold Sponsor",
"version_file_read_failed": "Failed to read version file"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "Let all mods shine here",
"mod_submit_title": "Mod Submission",
"mod_type_label": "Mod Type",
"mod_type_client": "Client Mod",
"mod_type_server": "Server Mod",
"modid_label": "Modid",
"modid_placeholder": "Please enter Modid (separated by commas) or upload files to automatically obtain",
"modid_count": "Currently added {count} Modids",
"upload_file_label": "Upload File",
"upload_file_hint": "Click or drag files to this area to upload",
"upload_file_support": "Supports .jar format files, multiple selection allowed",
"file_selected": "{count} files selected",
"start_upload": "Start Upload",
"uploading": "Uploading...",
"submit": "Submit {type} Mod",
"submitting": "Submitting...",
"submit_confirm_title": "Confirm Submission",
"submit_confirm_content": "Are you sure you want to submit {count} {type} mods?",
"please_select_file": "Please select files first",
"upload_success": "Successfully uploaded {count} files",
"data_format_error": "Returned data format error",
"upload_failed": "Upload failed",
"upload_error": "Upload error, please try again",
"submit_success": "{type} mod submitted successfully",
"submit_failed": "Submission failed",
"submit_error": "Submission error, please try again"
},
"message": {
"backend_running": "DeEarthX.Core is already running",
"backend_started": "DeEarthX.Core started successfully",
"backend_port_occupied": "Port 37019 is occupied by another application!",
"backend_start_failed": "DeEarthX.Core failed to start, please check if port 37019 is occupied! (Retried {count} times)",
"backend_restart": "DeEarthX.Core restarting!",
"retry_start": "Start failed, retrying ({current}/{max})...",
"config_load_error": "Failed to load configuration"
}
}

257
front/lang/es_es.json Normal file
View File

@@ -0,0 +1,257 @@
{
"common": {
"app_name": "DeEarthX",
"version": "Versión",
"status_loading": "Iniciando",
"status_success": "Normal",
"status_error": "Error",
"backend_status": "Estado del backend",
"confirm": "Confirmar",
"cancel": "Cancelar",
"submit": "Enviar",
"upload": "Subir",
"start": "Iniciar",
"save": "Guardar",
"delete": "Eliminar",
"edit": "Editar",
"close": "Cerrar",
"loading": "Cargando...",
"error": "Error",
"success": "Éxito",
"warning": "Advertencia",
"info": "Información"
},
"menu": {
"home": "Inicio",
"deearth": "Filtro",
"galaxy": "Enviar",
"template": "Plantilla",
"setting": "Configuración",
"about": "Acerca de"
},
"home": {
"title": "¡Crea servidores donde quieras, cuando quieras!",
"mode_title": "Selección de modo",
"upload_title": "Arrastra o haz clic para subir archivos",
"upload_hint": "Por favor usa archivos .zip (CurseForge, MCBBS) y .mrpack (Modrinth)",
"mode_server": "Modo servidor",
"mode_upload": "Modo subida",
"preparing": "Preparando...",
"task_preparing": "Conectando al servicio backend...",
"task_connecting": "Conexión exitosa al servicio backend, comenzando procesamiento de tareas...",
"step1_title": "Extraer pack",
"step1_desc": "Extraer contenido y descargar archivos",
"step2_title": "Filtrar mods",
"step2_desc": "Función principal de DeEarthX",
"step3_title": "Descargar servidor",
"step3_desc": "Instalar servidor de cargador de mods",
"step4_title": "Completado",
"step4_desc": "¡Todo listo!",
"progress_title": "Progreso de creación",
"upload_progress": "Progreso de subida",
"unzip_progress": "Progreso de extracción",
"download_progress": "Progreso de descarga",
"server_install_progress": "Instalación del servidor",
"server_install_step": "Paso",
"server_install_message": "Mensaje",
"server_install_completed": "¡Instalación completada!",
"server_install_error": "Error de instalación",
"server_install_duration": "Duración",
"filter_mods_progress": "Filtrado de mods",
"filter_mods_total": "Total de mods",
"filter_mods_current": "Verificando actualmente",
"filter_mods_completed": "¡Filtrado completado! Se identificaron {filtered} mods de cliente, se movieron {moved}",
"filter_mods_error": "Filtrado fallido",
"start_production": "Iniciar creación, ¡no cambies de menú!",
"production_complete": "¡Creación del servidor completada! Duración {time} segundos!",
"please_select_file": "Por favor arrastra o selecciona un archivo primero",
"only_zip_mrpack": "Solo se pueden subir archivos .zip y .mrpack",
"file_prepared": "Archivo preparado",
"preparing_file": "Preparando archivo...",
"ws_connecting": "Estableciendo conexión WebSocket...",
"ws_connected": "Conexión WebSocket exitosa",
"ws_failed": "Fallo en la conexión WebSocket",
"request_failed": "Fallo en la solicitud al servicio backend, verifica que el servicio backend esté en ejecución",
"backend_error": "¡DeEarthX.Core encontró un error fatal!",
"backend_error_desc": "Por favor toma una captura de pantalla completa de la ventana y envíala al grupo\nInformación de error: {error}",
"java_not_found": "¡Java no encontrado en las variables del sistema! Por favor instala Java, de lo contrario el modo servidor no se podrá usar!",
"unknown_step": "Paso desconocido",
"parse_error": "Fallo al analizar mensaje del servidor",
"speed": "Velocidad",
"remaining": "Tiempo restante",
"java_error_title": "Java no encontrado",
"java_error_desc": "¡Java no encontrado en las variables del sistema! Por favor instala Java, de lo contrario el modo servidor no se podrá usar!\n\nSugerencias:\n1. Descargar e instalar Java 17 o superior\n2. Configurar variables de entorno Java\n3. Reiniciar la aplicación",
"network_error_title": "Error de red",
"network_error_desc": "Problema de conexión de red\nInformación de error: {error}",
"file_error_title": "Error de archivo",
"file_error_desc": "Problema de operación de archivo\nInformación de error: {error}",
"memory_error_title": "Error de memoria",
"memory_error_desc": "Memoria insuficiente\nInformación de error: {error}",
"unknown_error_title": "Error desconocido",
"unknown_error_desc": "Ocurrió un error desconocido, por favor contacta al soporte técnico",
"ws_error_title": "Fallo en la conexión WebSocket",
"ws_error_desc": "No se puede establecer conexión WebSocket con el backend",
"suggestions": "Soluciones sugeridas",
"suggestion_check_network": "Verificar si la conexión de red es normal",
"suggestion_check_firewall": "Verificar configuración de firewall",
"suggestion_retry": "Intentar de nuevo más tarde",
"suggestion_check_disk_space": "Verificar si hay suficiente espacio en disco",
"suggestion_check_permission": "Verificar configuración de permisos de archivos",
"suggestion_check_file_format": "Confirmar que el formato de archivo es correcto",
"suggestion_increase_memory": "Aumentar memoria asignada a la aplicación",
"suggestion_close_other_apps": "Cerrar otras aplicaciones que usen memoria",
"suggestion_restart_application": "Reiniciar la aplicación",
"suggestion_check_backend": "Verificar si el servicio backend está funcionando normalmente",
"suggestion_check_logs": "Verificar archivos de registro para más información",
"suggestion_check_port": "Verificar si el puerto 37019 está ocupado",
"suggestion_contact_support": "Contactar soporte técnico para obtener ayuda",
"template_select_title": "Seleccionar plantilla de servidor",
"template_select_desc": "Elija una plantilla para su servidor. El uso de una plantilla omitirá la instalación del servidor y copiará directamente los archivos de la plantilla al directorio del servidor",
"template_official_loader": "Ninguno",
"template_official_loader_desc": "No usar ninguna plantilla, realizar instalación estándar del servidor",
"template_name": "Nombre de plantilla",
"template_description": "Descripción",
"template_author": "Autor",
"template_version": "Versión",
"template_selected": "Plantilla seleccionada",
"template_select_button": "Seleccionar plantilla",
"template_loading": "Cargando lista de plantillas...",
"template_load_failed": "Error al cargar la lista de plantillas",
"template_apply_success": "Plantilla aplicada con éxito",
"template_apply_failed": "Error al aplicar la plantilla"
},
"template": {
"title": "Gestión de plantillas",
"description": "Gestiona tus plantillas de servidor, crea, visualiza y elimina plantillas",
"create_button": "Crear plantilla",
"create_title": "Crear nueva plantilla",
"name": "Nombre de plantilla",
"name_required": "El nombre de la plantilla es obligatorio",
"name_placeholder": "Por favor, introduce el nombre de la plantilla",
"version": "Versión",
"version_placeholder": "Por favor, introduce el número de versión",
"description": "Descripción",
"description_placeholder": "Por favor, introduce la descripción de la plantilla",
"author": "Autor",
"author_placeholder": "Por favor, introduce el nombre del autor",
"delete_button": "Eliminar",
"delete_title": "Eliminar plantilla",
"delete_confirm": "¿Estás seguro de que quieres eliminar la plantilla \"{name}\"?",
"delete_warning": "Esta operación eliminará permanentemente esta plantilla y todos sus archivos, no se puede recuperar.",
"delete_success": "Plantilla eliminada con éxito",
"delete_failed": "Error al eliminar la plantilla",
"create_success": "Plantilla creada con éxito",
"create_failed": "Error al crear la plantilla",
"open_folder": "Abrir carpeta",
"open_folder_success": "Carpeta de plantilla abierta",
"open_folder_failed": "Error al abrir la carpeta",
"edit_button": "Editar",
"edit_title": "Editar plantilla",
"update_success": "Plantilla actualizada con éxito",
"update_failed": "Error al actualizar la plantilla",
"empty": "Aún no hay plantillas",
"empty_hint": "Haz clic en el botón de arriba para crear tu primera plantilla"
},
"setting": {
"title": "Configuración DeEarthX",
"subtitle": "¡Haz que DeEarthX V3 sea más adecuado para ti!",
"category_filter": "Configuración de filtrado de mods",
"category_mirror": "Configuración de fuente de descarga",
"category_system": "Configuración de gestión del sistema",
"filter_hashes_name": "Filtrado por hash",
"filter_hashes_desc": "Filtrar mods de cliente innecesarios (método de filtrado por hash)",
"filter_dexpub_name": "Filtrado Galaxy Square",
"filter_dexpub_desc": "Filtrar archivos de cliente registrados en la plataforma Galaxy Square",
"filter_modrinth_name": "Filtrado API Modrinth",
"filter_modrinth_desc": "Verificar compatibilidad cliente/servidor de mods a través de la API Modrinth",
"filter_mixins_name": "Filtrado Mixin",
"filter_mixins_desc": "Filtrar archivos relacionados con Client Mixins",
"mirror_mcimirror_name": "Fuente espejo MCIM",
"mirror_mcimirror_desc": "Usar fuente espejo MCIM para acelerar descargas",
"mirror_bmclapi_name": "Fuente espejo BMCLAPI",
"mirror_bmclapi_desc": "Usar fuente espejo BMCLAPI para acelerar descargas",
"system_oaf_name": "Abrir directorio después de la operación",
"system_oaf_desc": "Abrir automáticamente el directorio después de completar la creación del servidor",
"system_autozip_name": "Empaquetar automáticamente en zip",
"system_autozip_desc": "Empaquetar automáticamente en zip después de completar la creación del servidor (no empaquetar en modo servidor)",
"switch_on": "Activado",
"switch_off": "Desactivado",
"language_title": "Configuración de idioma",
"language_desc": "Elegir idioma de visualización de la interfaz",
"language_chinese": "Chino simplificado",
"language_english": "English",
"language_japanese": "日本語",
"language_french": "Français",
"language_german": "Deutsch",
"language_spanish": "Español",
"config_saved": "Configuración guardada",
"config_load_failed": "Fallo al cargar configuración",
"config_save_failed": "Fallo al guardar configuración",
"export_config": "Exportar configuración",
"import_config": "Importar configuración",
"config_exported": "Configuración exportada exitosamente",
"config_export_failed": "Fallo al exportar configuración",
"config_imported": "Configuración importada exitosamente",
"config_import_failed": "Fallo al importar configuración",
"config_invalid_format": "Formato de archivo de configuración inválido"
},
"about": {
"title": "Acerca de DeEarthX",
"subtitle": "Herramienta profesional para crear servidores de packs de mods de Minecraft",
"about_software": "Acerca del software",
"current_version": "Versión actual:",
"build_time": "Tiempo de construcción:",
"author": "Autor:",
"development_team": "Equipo de desarrollo",
"author_tianpao": "天跑",
"contribution_author": "Autor",
"dev2_xcc": "XCC",
"contribution_dev2": "2º desarrollador (mejoras y optimizaciones)",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "Espejo BMCLAPI",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "Espejo MCIM",
"sponsor": "Patrocinadores",
"sponsor_elfidc": "亿讯云",
"sponsor_type_gold": "Patrocinador oro",
"version_file_read_failed": "Fallo al leer archivo de versión"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "Haz que todos los mods brillen aquí",
"mod_submit_title": "Envío de mod",
"mod_type_label": "Tipo de mod",
"mod_type_client": "Mod de cliente",
"mod_type_server": "Mod de servidor",
"modid_label": "Modid",
"modid_placeholder": "Ingresa un Modid (separados por comas) o sube un archivo para obtener automáticamente",
"modid_count": "{count} Modids actualmente agregados",
"upload_file_label": "Subir archivo",
"upload_file_hint": "Haz clic o arrastra archivos aquí para subir",
"upload_file_support": "Soporta archivos .jar, selección múltiple disponible",
"file_selected": "{count} archivos seleccionados",
"start_upload": "Iniciar subida",
"uploading": "Subiendo...",
"submit": "Enviar mods {type}",
"submitting": "Enviando...",
"submit_confirm_title": "Confirmar envío",
"submit_confirm_content": "¿Estás seguro de que quieres enviar {count} mods {type}?",
"please_select_file": "Por favor selecciona un archivo primero",
"upload_success": "{count} archivos subidos exitosamente",
"data_format_error": "Error en formato de datos devueltos",
"upload_failed": "Fallo al subir",
"upload_error": "Error al subir, por favor intenta de nuevo",
"submit_success": "Envío exitoso de mods {type}",
"submit_failed": "Fallo al enviar",
"submit_error": "Error al enviar, por favor intenta de nuevo"
},
"message": {
"backend_running": "DeEarthX.Core ya se está ejecutando",
"backend_started": "DeEarthX.Core iniciado exitosamente",
"backend_port_occupied": "¡El puerto 37019 está ocupado por otra aplicación!",
"backend_start_failed": "Fallo al iniciar DeEarthX.Core, verifica si el puerto 37019 está ocupado (reintentado {count} veces)",
"backend_restart": "¡Reiniciando DeEarthX.Core!",
"retry_start": "Fallo al iniciar, reintentando ({current}/{max})...",
"config_load_error": "Fallo al cargar configuración"
}
}

257
front/lang/fr_fr.json Normal file
View File

@@ -0,0 +1,257 @@
{
"common": {
"app_name": "DeEarthX",
"version": "Version",
"status_loading": "Démarrage",
"status_success": "Normal",
"status_error": "Erreur",
"backend_status": "État du backend",
"confirm": "Confirmer",
"cancel": "Annuler",
"submit": "Soumettre",
"upload": "Télécharger",
"start": "Démarrer",
"save": "Enregistrer",
"delete": "Supprimer",
"edit": "Modifier",
"close": "Fermer",
"loading": "Chargement...",
"error": "Erreur",
"success": "Succès",
"warning": "Avertissement",
"info": "Information"
},
"menu": {
"home": "Accueil",
"deearth": "Filtre",
"galaxy": "Soumettre",
"template": "Modèle",
"setting": "Paramètres",
"about": "À propos"
},
"home": {
"title": "Créez des serveurs n'importe où, n'importe quand !",
"mode_title": "Sélection du mode",
"upload_title": "Glissez-déposez ou cliquez pour télécharger un fichier",
"upload_hint": "Veuillez utiliser des fichiers .zip (CurseForge, MCBBS) et .mrpack (Modrinth)",
"mode_server": "Mode serveur",
"mode_upload": "Mode téléchargement",
"preparing": "Préparation...",
"task_preparing": "Connexion au service backend...",
"task_connecting": "Connexion au service backend réussie, début du traitement de la tâche...",
"step1_title": "Extraire le pack",
"step1_desc": "Extraire le contenu et télécharger les fichiers",
"step2_title": "Filtrer les mods",
"step2_desc": "Fonctionnalité principale de DeEarthX",
"step3_title": "Télécharger le serveur",
"step3_desc": "Installer le serveur de chargeur de mods",
"step4_title": "Terminé",
"step4_desc": "Tout est prêt !",
"progress_title": "Progression de la création",
"upload_progress": "Progression du téléchargement",
"unzip_progress": "Progression de l'extraction",
"download_progress": "Progression du téléchargement",
"server_install_progress": "Installation du serveur",
"server_install_step": "Étape",
"server_install_message": "Message",
"server_install_completed": "Installation terminée !",
"server_install_error": "Échec de l'installation",
"server_install_duration": "Durée",
"filter_mods_progress": "Filtrage des mods",
"filter_mods_total": "Total des mods",
"filter_mods_current": "Vérification en cours",
"filter_mods_completed": "Filtrage terminé ! {filtered} mods clients identifiés, {moved} déplacés",
"filter_mods_error": "Échec du filtrage",
"start_production": "Démarrer la création, ne changez pas de menu !",
"production_complete": "Création du serveur terminée ! Durée {time} secondes !",
"please_select_file": "Veuillez d'abord glisser-déposer ou sélectionner un fichier",
"only_zip_mrpack": "Seuls les fichiers .zip et .mrpack peuvent être téléchargés",
"file_prepared": "Fichier prêt",
"preparing_file": "Préparation du fichier...",
"ws_connecting": "Établissement de la connexion WebSocket...",
"ws_connected": "Connexion WebSocket réussie",
"ws_failed": "Échec de la connexion WebSocket",
"request_failed": "Échec de la requête vers le service backend, vérifiez que le service backend est en cours d'exécution",
"backend_error": "DeEarthX.Core a rencontré une erreur fatale !",
"backend_error_desc": "Veuillez prendre une capture d'écran complète de la fenêtre et l'envoyer dans le groupe\nInformations d'erreur : {error}",
"java_not_found": "Java introuvable dans les variables système ! Veuillez installer Java, sinon le mode serveur ne pourra pas être utilisé !",
"unknown_step": "Étape inconnue",
"parse_error": "Échec de l'analyse du message du serveur",
"speed": "Vitesse",
"remaining": "Temps restant",
"java_error_title": "Java introuvable",
"java_error_desc": "Java introuvable dans les variables système ! Veuillez installer Java, sinon le mode serveur ne pourra pas être utilisé !\n\nSuggestions :\n1. Télécharger et installer Java 17 ou supérieur\n2. Configurer les variables d'environnement Java\n3. Redémarrer l'application",
"network_error_title": "Erreur réseau",
"network_error_desc": "Problème de connexion réseau\nInformations d'erreur : {error}",
"file_error_title": "Erreur de fichier",
"file_error_desc": "Problème d'opération de fichier\nInformations d'erreur : {error}",
"memory_error_title": "Erreur de mémoire",
"memory_error_desc": "Mémoire insuffisante\nInformations d'erreur : {error}",
"unknown_error_title": "Erreur inconnue",
"unknown_error_desc": "Une erreur inconnue s'est produite, veuillez contacter le support technique",
"ws_error_title": "Échec de la connexion WebSocket",
"ws_error_desc": "Impossible d'établir une connexion WebSocket avec le backend",
"suggestions": "Solutions suggérées",
"suggestion_check_network": "Vérifier si la connexion réseau est normale",
"suggestion_check_firewall": "Vérifier les paramètres du pare-feu",
"suggestion_retry": "Réessayer plus tard",
"suggestion_check_disk_space": "Vérifier si l'espace disque est suffisant",
"suggestion_check_permission": "Vérifier les paramètres de permissions de fichiers",
"suggestion_check_file_format": "Confirmer que le format de fichier est correct",
"suggestion_increase_memory": "Augmenter la mémoire allouée à l'application",
"suggestion_close_other_apps": "Fermer d'autres applications utilisant de la mémoire",
"suggestion_restart_application": "Redémarrer l'application",
"suggestion_check_backend": "Vérifier si le service backend fonctionne normalement",
"suggestion_check_logs": "Vérifier les fichiers journaux pour plus d'informations",
"suggestion_check_port": "Vérifier si le port 37019 est occupé",
"suggestion_contact_support": "Contacter le support technique pour obtenir de l'aide",
"template_select_title": "Sélectionner le modèle de serveur",
"template_select_desc": "Choisissez un modèle pour votre serveur. L'utilisation d'un modèle sautera l'installation du serveur et copiera directement les fichiers du modèle dans le répertoire du serveur",
"template_official_loader": "Aucun",
"template_official_loader_desc": "Ne pas utiliser de modèle, effectuer l'installation standard du serveur",
"template_name": "Nom du modèle",
"template_description": "Description",
"template_author": "Auteur",
"template_version": "Version",
"template_selected": "Modèle sélectionné",
"template_select_button": "Sélectionner le modèle",
"template_loading": "Chargement de la liste des modèles...",
"template_load_failed": "Échec du chargement de la liste des modèles",
"template_apply_success": "Modèle appliqué avec succès",
"template_apply_failed": "Échec de l'application du modèle"
},
"template": {
"title": "Gestion des modèles",
"description": "Gérez vos modèles de serveur, créez, affichez et supprimez des modèles",
"create_button": "Créer un modèle",
"create_title": "Créer un nouveau modèle",
"name": "Nom du modèle",
"name_required": "Le nom du modèle est requis",
"name_placeholder": "Veuillez entrer le nom du modèle",
"version": "Version",
"version_placeholder": "Veuillez entrer le numéro de version",
"description": "Description",
"description_placeholder": "Veuillez entrer la description du modèle",
"author": "Auteur",
"author_placeholder": "Veuillez entrer le nom de l'auteur",
"delete_button": "Supprimer",
"delete_title": "Supprimer le modèle",
"delete_confirm": "Êtes-vous sûr de vouloir supprimer le modèle \"{name}\" ?",
"delete_warning": "Cette opération supprimera définitivement ce modèle et tous ses fichiers, impossible à récupérer.",
"delete_success": "Modèle supprimé avec succès",
"delete_failed": "Échec de la suppression du modèle",
"create_success": "Modèle créé avec succès",
"create_failed": "Échec de la création du modèle",
"open_folder": "Ouvrir le dossier",
"open_folder_success": "Dossier du modèle ouvert",
"open_folder_failed": "Échec de l'ouverture du dossier",
"edit_button": "Modifier",
"edit_title": "Modifier le modèle",
"update_success": "Modèle mis à jour avec succès",
"update_failed": "Échec de la mise à jour du modèle",
"empty": "Aucun modèle pour le moment",
"empty_hint": "Cliquez sur le bouton ci-dessus pour créer votre premier modèle"
},
"setting": {
"title": "Paramètres DeEarthX",
"subtitle": "Rendez DeEarthX V3 plus adapté à vos besoins !",
"category_filter": "Paramètres de filtrage des mods",
"category_mirror": "Paramètres de la source de téléchargement",
"category_system": "Paramètres de gestion système",
"filter_hashes_name": "Filtrage par hachage",
"filter_hashes_desc": "Filtrer les mods client inutiles (méthode de filtrage par hachage)",
"filter_dexpub_name": "Filtrage Galaxy Square",
"filter_dexpub_desc": "Filtrer les fichiers client enregistrés sur la plateforme Galaxy Square",
"filter_modrinth_name": "Filtrage API Modrinth",
"filter_modrinth_desc": "Vérifier la compatibilité client/serveur des mods via l'API Modrinth",
"filter_mixins_name": "Filtrage Mixin",
"filter_mixins_desc": "Filtrer les fichiers liés aux Client Mixins",
"mirror_mcimirror_name": "Source miroir MCIM",
"mirror_mcimirror_desc": "Utiliser la source miroir MCIM pour accélérer le téléchargement",
"mirror_bmclapi_name": "Source miroir BMCLAPI",
"mirror_bmclapi_desc": "Utiliser la source miroir BMCLAPI pour accélérer le téléchargement",
"system_oaf_name": "Ouvrir le dossier après l'opération",
"system_oaf_desc": "Ouvrir automatiquement le dossier après la création du serveur",
"system_autozip_name": "Créer automatiquement un zip",
"system_autozip_desc": "Créer automatiquement un zip après la création du serveur (pas de zip en mode serveur)",
"switch_on": "Activé",
"switch_off": "Désactivé",
"language_title": "Paramètres de langue",
"language_desc": "Choisir la langue d'affichage de l'interface",
"language_chinese": "Chinois simplifié",
"language_english": "English",
"language_japanese": "日本語",
"language_french": "Français",
"language_german": "Deutsch",
"language_spanish": "Español",
"config_saved": "Configuration enregistrée",
"config_load_failed": "Échec du chargement de la configuration",
"config_save_failed": "Échec de l'enregistrement de la configuration",
"export_config": "Exporter la configuration",
"import_config": "Importer la configuration",
"config_exported": "Configuration exportée avec succès",
"config_export_failed": "Échec de l'exportation de la configuration",
"config_imported": "Configuration importée avec succès",
"config_import_failed": "Échec de l'importation de la configuration",
"config_invalid_format": "Format de fichier de configuration invalide"
},
"about": {
"title": "À propos de DeEarthX",
"subtitle": "Outil professionnel de création de serveur de pack de mods Minecraft",
"about_software": "À propos du logiciel",
"current_version": "Version actuelle :",
"build_time": "Heure de construction :",
"author": "Auteur :",
"development_team": "Équipe de développement",
"author_tianpao": "天跑",
"contribution_author": "Auteur",
"dev2_xcc": "XCC",
"contribution_dev2": "2ème développeur (améliorations et optimisations)",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "Miroir BMCLAPI",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "Miroir MCIM",
"sponsor": "Sponsors",
"sponsor_elfidc": "亿讯云",
"sponsor_type_gold": "Sponsor or",
"version_file_read_failed": "Échec de la lecture du fichier de version"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "Faire briller tous les mods ici",
"mod_submit_title": "Soumission de mod",
"mod_type_label": "Type de mod",
"mod_type_client": "Mod client",
"mod_type_server": "Mod serveur",
"modid_label": "Modid",
"modid_placeholder": "Entrez un Modid (séparés par des virgules) ou téléchargez un fichier pour obtenir automatiquement",
"modid_count": "{count} Modids actuellement ajoutés",
"upload_file_label": "Télécharger un fichier",
"upload_file_hint": "Cliquez ou faites glisser un fichier ici pour le télécharger",
"upload_file_support": "Supporte les fichiers .jar, sélection multiple possible",
"file_selected": "{count} fichiers sélectionnés",
"start_upload": "Commencer le téléchargement",
"uploading": "Téléchargement en cours...",
"submit": "Soumettre mods {type}",
"submitting": "Soumission en cours...",
"submit_confirm_title": "Confirmer la soumission",
"submit_confirm_content": "Êtes-vous sûr de vouloir soumettre {count} mods {type} ?",
"please_select_file": "Veuillez d'abord sélectionner un fichier",
"upload_success": "{count} fichiers téléchargés avec succès",
"data_format_error": "Erreur de format des données renvoyées",
"upload_failed": "Échec du téléchargement",
"upload_error": "Erreur de téléchargement, veuillez réessayer",
"submit_success": "Soumission réussie des mods {type}",
"submit_failed": "Échec de la soumission",
"submit_error": "Erreur de soumission, veuillez réessayer"
},
"message": {
"backend_running": "DeEarthX.Core est déjà en cours d'exécution",
"backend_started": "DeEarthX.Core démarré avec succès",
"backend_port_occupied": "Le port 37019 est occupé par une autre application !",
"backend_start_failed": "Échec du démarrage de DeEarthX.Core, vérifiez si le port 37019 est occupé ! (Réessayé {count} fois)",
"backend_restart": "Redémarrage de DeEarthX.Core !",
"retry_start": "Échec du démarrage, nouvelle tentative ({current}/{max})...",
"config_load_error": "Échec du chargement de la configuration"
}
}

265
front/lang/ja_jp.json Normal file
View File

@@ -0,0 +1,265 @@
{
"common": {
"app_name": "DeEarthX",
"version": "バージョン",
"status_loading": "起動中",
"status_success": "正常",
"status_error": "エラー",
"backend_status": "バックエンド状態",
"confirm": "確認",
"cancel": "キャンセル",
"submit": "送信",
"upload": "アップロード",
"start": "開始",
"save": "保存",
"delete": "削除",
"edit": "編集",
"close": "閉じる",
"loading": "読み込み中...",
"error": "エラー",
"success": "成功",
"warning": "警告",
"info": "情報"
},
"menu": {
"home": "ホーム",
"deearth": "フィルター",
"galaxy": "送信",
"template": "テンプレート",
"setting": "設定",
"about": "について"
},
"home": {
"title": "サーバー設定をいつでもどこで!",
"mode_title": "モード選択",
"upload_title": "ドラッグまたはクリックしてファイルをアップロード",
"upload_hint": ".zipCurseForge、MCBBSと.mrpackModrinthファイルを使用してください",
"mode_server": "サーバー設定モード",
"mode_upload": "アップロードモード",
"preparing": "準備中...",
"task_preparing": "バックエンドサービスに接続中...",
"task_connecting": "バックエンドサービスへの接続に成功、タスク処理を開始...",
"step1_title": "統合パックを展開",
"step1_desc": "内容を展開してファイルをダウンロード",
"step2_title": "Modをフィルタリング",
"step2_desc": "DeEarthXの核心機能",
"step3_title": "サーバーをダウンロード",
"step3_desc": "Modローダーサーバーをインストール",
"step4_title": "完了",
"step4_desc": "準備完了!",
"progress_title": "制作進度",
"upload_progress": "アップロード進度",
"unzip_progress": "展開進度",
"download_progress": "ダウンロード進捗",
"server_install_progress": "サーバーインストール",
"server_install_step": "ステップ",
"server_install_message": "メッセージ",
"server_install_completed": "インストール完了!",
"server_install_error": "インストール失敗",
"server_install_duration": "所要時間",
"filter_mods_progress": "Modフィルタリング",
"filter_mods_total": "総Mod数",
"filter_mods_current": "現在チェック中",
"filter_mods_completed": "フィルタリング完了!{filtered}個のクライアントModを識別、{moved}個を移動",
"filter_mods_error": "フィルタリング失敗",
"start_production": "制作を開始、メニューを切り替えないでください!",
"production_complete": "サーバー制作完了!所要時間{time}秒!",
"please_select_file": "ファイルをドラッグまたは選択してください",
"only_zip_mrpack": ".zipと.mrpackファイルのみアップロード可能",
"file_prepared": "ファイルの準備完了",
"preparing_file": "ファイルを準備中...",
"ws_connecting": "WebSocket接続を確立中...",
"ws_connected": "WebSocket接続成功",
"ws_failed": "WebSocket接続失敗",
"request_failed": "バックエンドサービスへのリクエストが失敗しました。バックエンドサービスが実行されているか確認してください",
"backend_error": "DeEarthX.Coreで致命的なエラーが発生しました",
"backend_error_desc": "ウィンドウ全体のスクリーンショットをグループに送ってください\nエラー情報{error}",
"java_not_found": "システム変数にJavaが見つかりませんJavaをインストールしてください。そうしないとサーバー設定モードは使用できません",
"unknown_step": "不明なステップ",
"parse_error": "サーバーメッセージの解析に失敗しました",
"speed": "速度",
"remaining": "残り時間",
"java_error_title": "Javaが見つかりません",
"java_error_desc": "システム変数にJavaが見つかりませんJavaをインストールしてください。そうしないとサーバー設定モードは使用できません\n\n提案\n1. Java 17以上をダウンロードしてインストール\n2. Java環境変数を設定\n3. アプリケーションを再起動",
"network_error_title": "ネットワークエラー",
"network_error_desc": "ネットワーク接続に問題があります\nエラー情報{error}",
"file_error_title": "ファイルエラー",
"file_error_desc": "ファイル操作に問題があります\nエラー情報{error}",
"memory_error_title": "メモリエラー",
"memory_error_desc": "メモリ不足\nエラー情報{error}",
"unknown_error_title": "不明なエラー",
"unknown_error_desc": "不明なエラーが発生しました。技術サポートに連絡してください",
"ws_error_title": "WebSocket接続失敗",
"ws_error_desc": "バックエンドとのWebSocket接続を確立できません",
"suggestions": "提案された解決策",
"suggestion_check_network": "ネットワーク接続が正常か確認",
"suggestion_check_firewall": "ファイアウォール設定を確認",
"suggestion_retry": "後でもう一度試す",
"suggestion_check_disk_space": "ディスク容量が十分か確認",
"suggestion_check_permission": "ファイル権限設定を確認",
"suggestion_check_file_format": "ファイル形式が正しいか確認",
"suggestion_increase_memory": "アプリケーションに割り当てるメモリを増やす",
"suggestion_close_other_apps": "メモリを使用する他のアプリケーションを閉じる",
"suggestion_restart_application": "アプリケーションを再起動",
"suggestion_check_backend": "バックエンドサービスが正常に動作しているか確認",
"suggestion_check_logs": "詳細情報のためにログファイルを確認",
"suggestion_check_port": "ポート37019が使用されているか確認",
"suggestion_contact_support": "技術サポートに連絡してヘルプを受ける",
"template_select_title": "サーバーテンプレートを選択",
"template_select_desc": "サーバー用のテンプレートを選択してください。テンプレートを使用するとサーバーインストールがスキップされ、テンプレートファイルがサーバーディレクトリに直接コピーされます",
"template_official_loader": "なし",
"template_official_loader_desc": "テンプレートを使用せず、標準のサーバーインストールを実行",
"template_name": "テンプレート名",
"template_description": "説明",
"template_author": "作者",
"template_version": "バージョン",
"template_selected": "選択されたテンプレート",
"template_select_button": "テンプレートを選択",
"template_loading": "テンプレートリストを読み込み中...",
"template_load_failed": "テンプレートリストの読み込みに失敗",
"template_apply_success": "テンプレートの適用に成功",
"template_apply_failed": "テンプレートの適用に失敗"
},
"template": {
"title": "テンプレート管理",
"description": "サーバーテンプレートを管理し、作成、表示、削除します",
"create_button": "テンプレートを作成",
"create_title": "新しいテンプレートを作成",
"name": "テンプレート名",
"name_required": "テンプレート名は必須です",
"name_placeholder": "テンプレート名を入力してください",
"version": "バージョン",
"version_placeholder": "バージョン番号を入力してください",
"description": "説明",
"description_placeholder": "テンプレートの説明を入力してください",
"author": "作者",
"author_placeholder": "作者名を入力してください",
"delete_button": "削除",
"delete_title": "テンプレートを削除",
"delete_confirm": "テンプレート \"{name}\" を削除してもよろしいですか?",
"delete_warning": "この操作はテンプレートとそのすべてのファイルを永久に削除し、復元できません。",
"delete_success": "テンプレートを削除しました",
"delete_failed": "テンプレートの削除に失敗しました",
"create_success": "テンプレートを作成しました",
"create_failed": "テンプレートの作成に失敗しました",
"open_folder": "フォルダーを開く",
"open_folder_success": "テンプレートフォルダーを開きました",
"open_folder_failed": "フォルダーを開くことに失敗しました",
"edit_button": "編集",
"edit_title": "テンプレートを編集",
"update_success": "テンプレートを更新しました",
"update_failed": "テンプレートの更新に失敗しました",
"empty": "まだテンプレートがありません",
"empty_hint": "上のボタンをクリックして最初のテンプレートを作成してください",
"local_templates": "ローカルテンプレート",
"template_store": "テンプレートストア",
"store_empty": "テンプレートストアは空です",
"store_empty_hint": "現在利用可能なテンプレートはありません",
"install_button": "インストール",
"install_success": "テンプレートのインストールに成功しました",
"install_failed": "テンプレートのインストールに失敗しました",
"store_load_failed": "テンプレートストアの読み込みに失敗しました"
},
"setting": {
"title": "DeEarthX 設定",
"subtitle": "DeEarthX V3をあなたに合わせて",
"category_filter": "Modフィルタリング設定",
"category_mirror": "ダウンロードソース設定",
"category_system": "システム管理設定",
"filter_hashes_name": "ハッシュフィルタリング",
"filter_hashes_desc": "不要なクライアントModをフィルタリングハッシュフィルタリング法",
"filter_dexpub_name": "Galaxy Squareフィルタリング",
"filter_dexpub_desc": "Galaxy Squareプラットフォームに記録されたクライアントファイルをフィルタリング",
"filter_modrinth_name": "Modrinth APIフィルタリング",
"filter_modrinth_desc": "Modrinth APIでModのクライアント/サーバー互換性を確認",
"filter_mixins_name": "Mixinフィルタリング",
"filter_mixins_desc": "Client Mixin関連ファイルをフィルタリング",
"mirror_mcimirror_name": "MCIMミラーソース",
"mirror_mcimirror_desc": "MCIMミラーソースを使用してダウンロードを加速",
"mirror_bmclapi_name": "BMCLAPIミラーソース",
"mirror_bmclapi_desc": "BMCLAPIミラーソースを使用してダウンロードを加速",
"system_oaf_name": "操作完了後にディレクトリを開く",
"system_oaf_desc": "サーバー制作完了後に自動的にディレクトリを開く",
"system_autozip_name": "自動的にzipにパッケージ",
"system_autozip_desc": "サーバー制作完了後に自動的にzipにパッケージサーバー設定モードではパッケージしない",
"switch_on": "オン",
"switch_off": "オフ",
"language_title": "言語設定",
"language_desc": "インターフェース表示言語を選択",
"language_chinese": "簡体字中国語",
"language_english": "English",
"language_japanese": "日本語",
"language_french": "Français",
"language_german": "Deutsch",
"language_spanish": "Español",
"config_saved": "設定が保存されました",
"config_load_failed": "設定の読み込みに失敗しました",
"config_save_failed": "設定の保存に失敗しました",
"export_config": "設定をエクスポート",
"import_config": "設定をインポート",
"config_exported": "設定のエクスポートに成功しました",
"config_export_failed": "設定のエクスポートに失敗しました",
"config_imported": "設定のインポートに成功しました",
"config_import_failed": "設定のインポートに失敗しました",
"config_invalid_format": "設定ファイルの形式が無効です"
},
"about": {
"title": "DeEarthXについて",
"subtitle": "プロフェッショナルなMinecraft統合パックサーバー制作ツール",
"about_software": "ソフトウェアについて",
"current_version": "現在のバージョン:",
"build_time": "ビルド時間:",
"author": "作者:",
"development_team": "開発チーム",
"author_tianpao": "天跑",
"contribution_author": "作者",
"dev2_xcc": "XCC",
"contribution_dev2": "2番目の開発者改善と最適化",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPIミラー",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIMミラー",
"sponsor": "スポンサー",
"sponsor_elfidc": "亿讯云",
"sponsor_type_gold": "ゴールドスポンサー",
"version_file_read_failed": "バージョンファイルの読み込みに失敗しました"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "すべてのModをここで輝かせる",
"mod_submit_title": "Mod送信",
"mod_type_label": "Modタイプ",
"mod_type_client": "クライアントMod",
"mod_type_server": "サーバーMod",
"modid_label": "Modid",
"modid_placeholder": "Modidを入力してください複数はカンマで区切るまたはファイルをアップロードして自動取得",
"modid_count": "現在{count}個のModidが追加されています",
"upload_file_label": "ファイルをアップロード",
"upload_file_hint": "クリックまたはドラッグしてファイルをアップロード",
"upload_file_support": ".jar形式ファイルをサポート、複数選択可能",
"file_selected": "{count}個のファイルが選択されています",
"start_upload": "アップロード開始",
"uploading": "アップロード中...",
"submit": "{type}Modを送信",
"submitting": "送信中...",
"submit_confirm_title": "送信確認",
"submit_confirm_content": "{count}個の{type}Modを送信してもよろしいですか",
"please_select_file": "先にファイルを選択してください",
"upload_success": "{count}個のファイルのアップロードに成功しました",
"data_format_error": "返されたデータ形式が間違っています",
"upload_failed": "アップロードに失敗しました",
"upload_error": "アップロードエラー、再試行してください",
"submit_success": "{type}Modの送信に成功しました",
"submit_failed": "送信に失敗しました",
"submit_error": "送信エラー、再試行してください"
},
"message": {
"backend_running": "DeEarthX.Coreは実行中です",
"backend_started": "DeEarthX.Coreの起動に成功しました",
"backend_port_occupied": "ポート37019が他のアプリケーションによって使用されています",
"backend_start_failed": "DeEarthX.Coreの起動に失敗しました。ポート37019が使用されているか確認してください{count}回再試行しました)",
"backend_restart": "DeEarthX.Coreを再起動します",
"retry_start": "起動に失敗しました、再試行中 ({current}/{max})...",
"config_load_error": "設定の読み込みに失敗しました"
}
}

271
front/lang/zh_cn.json Normal file
View File

@@ -0,0 +1,271 @@
{
"common": {
"app_name": "DeEarthX",
"version": "版本",
"status_loading": "启动中",
"status_success": "正常",
"status_error": "错误",
"backend_status": "后端状态",
"confirm": "确定",
"cancel": "取消",
"submit": "提交",
"upload": "上传",
"start": "开始",
"save": "保存",
"delete": "删除",
"edit": "编辑",
"close": "关闭",
"loading": "加载中...",
"error": "错误",
"success": "成功",
"warning": "警告",
"info": "提示"
},
"menu": {
"home": "主页",
"deearth": "筛选",
"galaxy": "提交",
"template": "模板",
"setting": "设置",
"about": "关于"
},
"home": {
"title": "让开服变得随时随地!",
"mode_title": "模式选择",
"upload_title": "拖拽或点击上传文件",
"upload_hint": "支持 .zipCurseForge、MCBBS和 .mrpackModrinth格式文件",
"mode_server": "开服模式",
"mode_upload": "上传模式",
"preparing": "准备中...",
"task_preparing": "正在连接后端服务...",
"task_connecting": "后端服务连接成功,开始处理任务...",
"step1_title": "解压整合包",
"step1_desc": "解压内容并下载文件",
"step2_title": "筛选模组",
"step2_desc": "DeEarthX 的核心功能",
"step3_title": "下载服务端",
"step3_desc": "安装模组加载器服务端",
"step4_title": "完成",
"step4_desc": "一切就绪!",
"progress_title": "制作进度",
"upload_progress": "上传进度",
"unzip_progress": "解压进度",
"download_progress": "下载进度",
"server_install_progress": "服务端安装",
"server_install_step": "步骤",
"server_install_message": "消息",
"server_install_completed": "安装完成!",
"server_install_error": "安装失败",
"server_install_duration": "耗时",
"filter_mods_progress": "筛选模组",
"filter_mods_total": "总模组数",
"filter_mods_current": "当前检查",
"filter_mods_completed": "筛选完成!识别到 {filtered} 个客户端模组,移动 {moved} 个",
"filter_mods_error": "筛选失败",
"start_production": "开始制作,请勿切换菜单!",
"production_complete": "服务端制作完成!共用时 {time} 秒!",
"please_select_file": "请先拖拽或选择文件",
"only_zip_mrpack": "仅支持 .zip 和 .mrpack 文件",
"file_prepared": "文件准备完成",
"preparing_file": "正在准备文件...",
"ws_connecting": "正在建立 WebSocket 连接...",
"ws_connected": "WebSocket 连接成功",
"ws_failed": "WebSocket 连接失败",
"request_failed": "请求后端服务失败,请检查后端服务是否运行",
"backend_error": "DeEarthX.Core 遇到致命错误!",
"backend_error_desc": "请将整个窗口截图发在群里\n错误信息{error}",
"java_not_found": "未在系统变量中找到 Java请安装 Java否则开服模式将无法使用",
"unknown_step": "未知步骤",
"parse_error": "解析服务器消息失败",
"speed": "速度",
"remaining": "剩余时间",
"java_error_title": "Java 未找到",
"java_error_desc": "未在系统变量中找到 Java请安装 Java否则开服模式将无法使用\n\n建议\n1. 下载并安装 Java 17 或更高版本\n2. 配置 Java 环境变量\n3. 重启应用程序",
"network_error_title": "网络错误",
"network_error_desc": "网络连接出现问题\n错误信息{error}",
"file_error_title": "文件错误",
"file_error_desc": "文件操作出现问题\n错误信息{error}",
"memory_error_title": "内存错误",
"memory_error_desc": "内存不足\n错误信息{error}",
"unknown_error_title": "未知错误",
"unknown_error_desc": "发生未知错误,请联系技术支持",
"ws_error_title": "WebSocket 连接失败",
"ws_error_desc": "无法建立与后端的 WebSocket 连接",
"suggestions": "建议解决方案",
"suggestion_check_network": "检查网络连接是否正常",
"suggestion_check_firewall": "检查防火墙设置",
"suggestion_retry": "稍后重试",
"suggestion_check_disk_space": "检查磁盘空间是否充足",
"suggestion_check_permission": "检查文件权限设置",
"suggestion_check_file_format": "确认文件格式正确",
"suggestion_increase_memory": "增加分配给应用程序的内存",
"suggestion_close_other_apps": "关闭其他占用内存的应用程序",
"suggestion_restart_application": "重启应用程序",
"suggestion_check_backend": "检查后端服务是否正常运行",
"suggestion_check_logs": "查看日志文件获取更多信息",
"suggestion_check_port": "检查端口 37019 是否被占用",
"suggestion_contact_support": "联系技术支持获取帮助",
"template_select_title": "选择服务端模板",
"template_select_desc": "为您的服务器选择一个模板,使用模板将跳过服务端安装,直接复制模板文件到服务器目录",
"template_official_loader": "标准安装",
"template_official_loader_desc": "不使用任何模板,进行标准的服务端安装流程",
"template_name": "模板名称",
"template_description": "描述",
"template_author": "作者",
"template_version": "版本",
"template_selected": "已选择模板",
"template_select_button": "选择模板",
"template_loading": "加载模板列表中...",
"template_load_failed": "加载模板列表失败",
"template_apply_success": "模板应用成功",
"template_apply_failed": "模板应用失败",
"template_import_title": "导入模板",
"template_import_hint": "拖拽或点击上传模板 zip 文件",
"template_import_success": "模板导入成功",
"template_import_failed": "模板导入失败",
"template_export_button": "导出",
"template_export_success": "模板导出成功",
"template_export_failed": "模板导出失败",
"template_export_progress": "正在导出模板...",
"template_import_progress": "正在导入模板...",
"template_download_progress": "正在下载模板..."
},
"template": {
"title": "模板管理",
"description": "管理您的服务端模板,创建、查看和删除模板",
"create_button": "创建模板",
"create_title": "创建新模板",
"name": "模板名称",
"name_required": "模板名称不能为空",
"name_placeholder": "请输入模板名称",
"version": "版本",
"version_placeholder": "请输入版本号",
"description": "描述",
"description_placeholder": "请输入模板描述",
"author": "作者",
"author_placeholder": "请输入作者名称",
"delete_button": "删除",
"delete_title": "删除模板",
"delete_confirm": "确定要删除模板 \"{name}\" 吗?",
"delete_warning": "此操作将永久删除该模板及其所有文件,无法恢复。",
"delete_success": "模板删除成功",
"delete_failed": "模板删除失败",
"create_success": "模板创建成功",
"create_failed": "模板创建失败",
"open_folder": "打开文件夹",
"open_folder_success": "已打开模板文件夹",
"open_folder_failed": "打开文件夹失败",
"edit_button": "编辑",
"edit_title": "编辑模板",
"update_success": "模板更新成功",
"update_failed": "模板更新失败",
"empty": "还没有模板哦~",
"empty_hint": "点击上方按钮创建你的第一个模板",
"local_templates": "本地模板",
"template_store": "模板商店",
"store_empty": "模板商店为空",
"store_empty_hint": "暂时没有可用的模板",
"install_button": "安装",
"install_success": "模板安装成功",
"install_failed": "模板安装失败",
"store_load_failed": "加载模板商店失败"
},
"setting": {
"title": "DeEarthX 设置",
"subtitle": "让 DeEarthX V3 更适合你!",
"category_filter": "模组筛选设置",
"category_mirror": "下载源设置",
"category_system": "系统管理设置",
"filter_hashes_name": "哈希过滤",
"filter_hashes_desc": "过滤不必要的客户端模组(哈希过滤法)",
"filter_dexpub_name": "Galaxy Square 过滤",
"filter_dexpub_desc": "过滤 Galaxy Square 平台中记录的客户端文件",
"filter_modrinth_name": "Modrinth API 过滤",
"filter_modrinth_desc": "通过 Modrinth API 检查模组的客户端/服务端兼容性",
"filter_mixins_name": "Mixin 过滤",
"filter_mixins_desc": "过滤 Client Mixin 相关文件",
"mirror_mcimirror_name": "MCIM 镜像源",
"mirror_mcimirror_desc": "使用 MCIM 镜像源加速下载",
"mirror_bmclapi_name": "BMCLAPI 镜像源",
"mirror_bmclapi_desc": "使用 BMCLAPI 镜像源加速下载",
"system_oaf_name": "操作完成后打开目录",
"system_oaf_desc": "服务端制作完成后自动打开目录",
"system_autozip_name": "自动打包成 zip",
"system_autozip_desc": "服务端制作完成后自动打包成 zip开服模式不打包",
"switch_on": "开",
"switch_off": "关",
"language_title": "语言设置",
"language_desc": "选择界面显示语言",
"language_chinese": "简体中文",
"language_english": "English",
"config_saved": "配置已保存",
"config_load_failed": "加载配置失败",
"config_save_failed": "保存配置失败",
"export_config": "导出配置",
"import_config": "导入配置",
"config_exported": "配置导出成功",
"config_export_failed": "配置导出失败",
"config_imported": "配置导入成功",
"config_import_failed": "配置导入失败",
"config_invalid_format": "配置文件格式无效"
},
"about": {
"title": "关于 DeEarthX",
"subtitle": "专业的 Minecraft 整合包服务端制作工具",
"about_software": "关于软件",
"current_version": "当前版本:",
"build_time": "构建时间:",
"author": "作者:",
"development_team": "开发团队",
"author_tianpao": "天跑",
"contribution_author": "作者",
"dev2_xcc": "XCC",
"contribution_dev2": "2号开发改良与优化",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPI 镜像",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIM 镜像",
"sponsor": "赞助商",
"sponsor_elfidc": "亿讯云",
"sponsor_type_gold": "金牌赞助",
"version_file_read_failed": "版本文件读取失败"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "让所有模组都在这里发光",
"mod_submit_title": "模组提交",
"mod_type_label": "模组类型",
"mod_type_client": "客户端模组",
"mod_type_server": "服务端模组",
"modid_label": "Modid",
"modid_placeholder": "请输入 Modid多个用逗号分隔或上传文件自动获取",
"modid_count": "当前已添加 {count} 个 Modid",
"upload_file_label": "上传文件",
"upload_file_hint": "点击或拖拽文件到此区域上传",
"upload_file_support": "支持 .jar 格式文件,可多选",
"file_selected": "已选择 {count} 个文件",
"start_upload": "开始上传",
"uploading": "上传中...",
"submit": "提交 {type} 模组",
"submitting": "提交中...",
"submit_confirm_title": "确认提交",
"submit_confirm_content": "确定要提交 {count} 个 {type} 模组吗?",
"please_select_file": "请先选择文件",
"upload_success": "成功上传 {count} 个文件",
"data_format_error": "返回数据格式错误",
"upload_failed": "上传失败",
"upload_error": "上传出错,请重试",
"submit_success": "{type} 模组提交成功",
"submit_failed": "提交失败",
"submit_error": "提交出错,请重试"
},
"message": {
"backend_running": "DeEarthX.Core 已在运行",
"backend_started": "DeEarthX.Core 启动成功",
"backend_port_occupied": "37019 端口被其他应用占用!",
"backend_start_failed": "DeEarthX.Core 启动失败,请检查 37019 端口是否被占用!(已重试 {count} 次)",
"backend_restart": "DeEarthX.Core 重新启动!",
"retry_start": "启动失败,正在重试 ({current}/{max})...",
"config_load_error": "加载配置失败"
}
}

253
front/lang/zh_hk.json Normal file
View File

@@ -0,0 +1,253 @@
{
"common": {
"app_name": "DeEarthX",
"version": "版本",
"status_loading": "啟動中",
"status_success": "正常",
"status_error": "錯誤",
"backend_status": "後端狀態",
"confirm": "確定",
"cancel": "取消",
"submit": "提交",
"upload": "上傳",
"start": "開始",
"save": "儲存",
"delete": "刪除",
"edit": "編輯",
"close": "關閉",
"loading": "載入中...",
"error": "錯誤",
"success": "成功",
"warning": "警告",
"info": "提示"
},
"menu": {
"home": "主頁",
"deearth": "篩選",
"galaxy": "提交",
"template": "模板",
"setting": "設置",
"about": "關於"
},
"home": {
"title": "讓開服變得隨時隨地!",
"mode_title": "模式選擇",
"upload_title": "拖曳或點擊上傳檔案",
"upload_hint": "支援 .zipCurseForge、MCBBS和 .mrpackModrinth格式檔案",
"mode_server": "開服模式",
"mode_upload": "上傳模式",
"preparing": "準備中...",
"task_preparing": "正在連接後端服務...",
"task_connecting": "後端服務連接成功,開始處理任務...",
"step1_title": "解壓整合包",
"step1_desc": "解壓內容並下載檔案",
"step2_title": "篩選模組",
"step2_desc": "DeEarthX 的核心功能",
"step3_title": "下載伺服器",
"step3_desc": "安裝模組載入器伺服器",
"step4_title": "完成",
"step4_desc": "一切就緒!",
"progress_title": "製作進度",
"upload_progress": "上傳進度",
"unzip_progress": "解壓進度",
"download_progress": "下載進度",
"server_install_progress": "伺服器安裝",
"server_install_step": "步驟",
"server_install_message": "訊息",
"server_install_completed": "安裝完成!",
"server_install_error": "安裝失敗",
"server_install_duration": "耗時",
"filter_mods_progress": "篩選模組",
"filter_mods_total": "總模組數",
"filter_mods_current": "目前檢查",
"filter_mods_completed": "篩選完成!識別到 {filtered} 個客戶端模組,移動 {moved} 個",
"filter_mods_error": "篩選失敗",
"start_production": "開始製作,請勿切換選單!",
"production_complete": "伺服器製作完成!共用時 {time} 秒!",
"please_select_file": "請先拖曳或選擇檔案",
"only_zip_mrpack": "僅支援 .zip 和 .mrpack 檔案",
"file_prepared": "檔案準備完成",
"preparing_file": "正在準備檔案...",
"ws_connecting": "正在建立 WebSocket 連接...",
"ws_connected": "WebSocket 連接成功",
"ws_failed": "WebSocket 連接失敗",
"request_failed": "請求後端服務失敗,請檢查後端服務是否運行",
"backend_error": "DeEarthX.Core 遇到致命錯誤!",
"backend_error_desc": "請將整個視窗截圖發在群裡\n錯誤訊息{error}",
"java_not_found": "未在系統變數中找到 Java請安裝 Java否則開服模式將無法使用",
"unknown_step": "未知步驟",
"parse_error": "解析伺服器訊息失敗",
"speed": "速度",
"remaining": "剩餘時間",
"java_error_title": "Java 未找到",
"java_error_desc": "未在系統變數中找到 Java請安裝 Java否則開服模式將無法使用\n\n建議\n1. 下載並安裝 Java 17 或更高版本\n2. 配置 Java 環境變數\n3. 重新啟動應用程式",
"network_error_title": "網路錯誤",
"network_error_desc": "網路連接出現問題\n錯誤訊息{error}",
"file_error_title": "檔案錯誤",
"file_error_desc": "檔案操作出現問題\n錯誤訊息{error}",
"memory_error_title": "記憶體錯誤",
"memory_error_desc": "記憶體不足\n錯誤訊息{error}",
"unknown_error_title": "未知錯誤",
"unknown_error_desc": "發生未知錯誤,請聯繫技術支援",
"ws_error_title": "WebSocket 連接失敗",
"ws_error_desc": "無法建立與後端的 WebSocket 連接",
"suggestions": "建議解決方案",
"suggestion_check_network": "檢查網路連接是否正常",
"suggestion_check_firewall": "檢查防火牆設定",
"suggestion_retry": "稍後重試",
"suggestion_check_disk_space": "檢查磁碟空間是否充足",
"suggestion_check_permission": "檢查檔案權限設定",
"suggestion_check_file_format": "確認檔案格式正確",
"suggestion_increase_memory": "增加分配給應用程式的記憶體",
"suggestion_close_other_apps": "關閉其他佔用記憶體的應用程式",
"suggestion_restart_application": "重新啟動應用程式",
"suggestion_check_backend": "檢查後端服務是否正常運行",
"suggestion_check_logs": "查看日誌檔案獲取更多資訊",
"suggestion_check_port": "檢查連接埠 37019 是否被佔用",
"suggestion_contact_support": "聯繫技術支援獲取幫助",
"template_select_title": "選擇服務器模板",
"template_select_desc": "為您的服務器選擇一個模板,使用模板將跳過服務器安裝,直接複製模板檔案到服務器目錄",
"template_official_loader": "無",
"template_official_loader_desc": "不使用任何模板,進行標準的服務器安裝流程",
"template_name": "模板名稱",
"template_description": "描述",
"template_author": "作者",
"template_version": "版本",
"template_selected": "已選擇模板",
"template_select_button": "選擇模板",
"template_loading": "載入模板列表中...",
"template_load_failed": "載入模板列表失敗",
"template_apply_success": "模板應用成功",
"template_apply_failed": "模板應用失敗"
},
"template": {
"title": "模板管理",
"description": "管理您的服務器模板,創建、查看和刪除模板",
"create_button": "創建模板",
"create_title": "創建新模板",
"name": "模板名稱",
"name_required": "模板名稱不能為空",
"name_placeholder": "請輸入模板名稱",
"version": "版本",
"version_placeholder": "請輸入版本號",
"description": "描述",
"description_placeholder": "請輸入模板描述",
"author": "作者",
"author_placeholder": "請輸入作者名稱",
"delete_button": "刪除",
"delete_title": "刪除模板",
"delete_confirm": "確定要刪除模板 \"{name}\" 嗎?",
"delete_warning": "此操作將永久刪除該模板及其所有文件,無法恢復。",
"delete_success": "模板刪除成功",
"delete_failed": "模板刪除失敗",
"create_success": "模板創建成功",
"create_failed": "模板創建失敗",
"open_folder": "打開文件夾",
"open_folder_success": "已打開模板文件夾",
"open_folder_failed": "打開文件夾失敗",
"edit_button": "編輯",
"edit_title": "編輯模板",
"update_success": "模板更新成功",
"update_failed": "模板更新失敗",
"empty": "還沒有模板",
"empty_hint": "點擊上方按鈕創建你的第一個模板"
},
"setting": {
"title": "DeEarthX 設定",
"subtitle": "讓 DeEarthX V3 更適合你!",
"category_filter": "模組篩選設定",
"category_mirror": "下載來源設定",
"category_system": "系統管理設定",
"filter_hashes_name": "哈希過濾",
"filter_hashes_desc": "過濾不必要的客戶端模組(哈希過濾法)",
"filter_dexpub_name": "Galaxy Square 過濾",
"filter_dexpub_desc": "過濾 Galaxy Square 平台中記錄的客戶端檔案",
"filter_modrinth_name": "Modrinth API 過濾",
"filter_modrinth_desc": "透過 Modrinth API 檢查模組的客戶端/伺服器相容性",
"filter_mixins_name": "Mixin 過濾",
"filter_mixins_desc": "過濾 Client Mixin 相關檔案",
"mirror_mcimirror_name": "MCIM 鏡像來源",
"mirror_mcimirror_desc": "使用 MCIM 鏡像來源加速下載",
"mirror_bmclapi_name": "BMCLAPI 鏡像來源",
"mirror_bmclapi_desc": "使用 BMCLAPI 鏡像來源加速下載",
"system_oaf_name": "操作完成後開啟目錄",
"system_oaf_desc": "伺服器製作完成後自動開啟目錄",
"system_autozip_name": "自動打包成 zip",
"system_autozip_desc": "伺服器製作完成後自動打包成 zip開服模式不打包",
"switch_on": "開",
"switch_off": "關",
"language_title": "語言設定",
"language_desc": "選擇介面顯示語言",
"language_chinese": "簡體中文",
"language_english": "English",
"config_saved": "設定已儲存",
"config_load_failed": "載入設定失敗",
"config_save_failed": "儲存設定失敗",
"export_config": "匯出設定",
"import_config": "匯入設定",
"config_exported": "設定匯出成功",
"config_export_failed": "設定匯出失敗",
"config_imported": "設定匯入成功",
"config_import_failed": "設定匯入失敗",
"config_invalid_format": "設定檔案格式無效"
},
"about": {
"title": "關於 DeEarthX",
"subtitle": "專業的 Minecraft 整合包伺服器製作工具",
"about_software": "關於軟體",
"current_version": "目前版本:",
"build_time": "建置時間:",
"author": "作者:",
"development_team": "開發團隊",
"author_tianpao": "天跑",
"contribution_author": "作者",
"dev2_xcc": "XCC",
"contribution_dev2": "2號開發改良與優化",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPI 鏡像",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIM 鏡像",
"sponsor": "贊助商",
"sponsor_elfidc": "億訊雲",
"sponsor_type_gold": "金牌贊助",
"version_file_read_failed": "版本檔案讀取失敗"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "讓所有模組都在這裡發光",
"mod_submit_title": "模組提交",
"mod_type_label": "模組類型",
"mod_type_client": "客戶端模組",
"mod_type_server": "伺服器模組",
"modid_label": "Modid",
"modid_placeholder": "請輸入 Modid多個用逗號分隔或上傳檔案自動獲取",
"modid_count": "目前已新增 {count} 個 Modid",
"upload_file_label": "上傳檔案",
"upload_file_hint": "點擊或拖曳檔案到此區域上傳",
"upload_file_support": "支援 .jar 格式檔案,可多選",
"file_selected": "已選擇 {count} 個檔案",
"start_upload": "開始上傳",
"uploading": "上傳中...",
"submit": "提交 {type} 模組",
"submitting": "提交中...",
"submit_confirm_title": "確認提交",
"submit_confirm_content": "確定要提交 {count} 個 {type} 模組嗎?",
"please_select_file": "請先選擇檔案",
"upload_success": "成功上傳 {count} 個檔案",
"data_format_error": "傳回資料格式錯誤",
"upload_failed": "上傳失敗",
"upload_error": "上傳出錯,請重試",
"submit_success": "{type} 模組提交成功",
"submit_failed": "提交失敗",
"submit_error": "提交出錯,請重試"
},
"message": {
"backend_running": "DeEarthX.Core 已在運行",
"backend_started": "DeEarthX.Core 啟動成功",
"backend_port_occupied": "37019 連接埠被其他應用程式佔用!",
"backend_start_failed": "DeEarthX.Core 啟動失敗,請檢查 37019 連接埠是否被佔用!(已重試 {count} 次)",
"backend_restart": "DeEarthX.Core 重新啟動!",
"retry_start": "啟動失敗,正在重試 ({current}/{max})...",
"config_load_error": "載入設定失敗"
}
}

253
front/lang/zh_tw.json Normal file
View File

@@ -0,0 +1,253 @@
{
"common": {
"app_name": "DeEarthX",
"version": "版本",
"status_loading": "啟動中",
"status_success": "正常",
"status_error": "錯誤",
"backend_status": "後端狀態",
"confirm": "確定",
"cancel": "取消",
"submit": "提交",
"upload": "上傳",
"start": "開始",
"save": "儲存",
"delete": "刪除",
"edit": "編輯",
"close": "關閉",
"loading": "載入中...",
"error": "錯誤",
"success": "成功",
"warning": "警告",
"info": "提示"
},
"menu": {
"home": "主頁",
"deearth": "篩選",
"galaxy": "提交",
"template": "模板",
"setting": "設置",
"about": "關於"
},
"home": {
"title": "讓開伺服器變得隨時隨地!",
"mode_title": "模式選擇",
"upload_title": "拖曳或點擊上傳檔案",
"upload_hint": "支援 .zipCurseForge、MCBBS和 .mrpackModrinth格式檔案",
"mode_server": "開伺服器模式",
"mode_upload": "上傳模式",
"preparing": "準備中...",
"task_preparing": "正在連線後端服務...",
"task_connecting": "後端服務連線成功,開始處理任務...",
"step1_title": "解壓縮整合包",
"step1_desc": "解壓縮內容並下載檔案",
"step2_title": "篩選模組",
"step2_desc": "DeEarthX 的核心功能",
"step3_title": "下載伺服器",
"step3_desc": "安裝模組載入器伺服器",
"step4_title": "完成",
"step4_desc": "一切就緒!",
"progress_title": "製作進度",
"upload_progress": "上傳進度",
"unzip_progress": "解壓縮進度",
"download_progress": "下載進度",
"server_install_progress": "伺服器安裝",
"server_install_step": "步驟",
"server_install_message": "訊息",
"server_install_completed": "安裝完成!",
"server_install_error": "安裝失敗",
"server_install_duration": "耗時",
"filter_mods_progress": "篩選模組",
"filter_mods_total": "總模組數",
"filter_mods_current": "目前檢查",
"filter_mods_completed": "篩選完成!識別到 {filtered} 個客戶端模組,移動 {moved} 個",
"filter_mods_error": "篩選失敗",
"start_production": "開始製作,請勿切換選單!",
"production_complete": "伺服器製作完成!共用時 {time} 秒!",
"please_select_file": "請先拖曳或選擇檔案",
"only_zip_mrpack": "僅支援 .zip 和 .mrpack 檔案",
"file_prepared": "檔案準備完成",
"preparing_file": "正在準備檔案...",
"ws_connecting": "正在建立 WebSocket 連線...",
"ws_connected": "WebSocket 連線成功",
"ws_failed": "WebSocket 連線失敗",
"request_failed": "請求後端服務失敗,請檢查後端服務是否執行",
"backend_error": "DeEarthX.Core 遇到致命錯誤!",
"backend_error_desc": "請將整個視窗截圖發在群裡\n錯誤訊息{error}",
"java_not_found": "未在系統變數中找到 Java請安裝 Java否則開伺服器模式將無法使用",
"unknown_step": "未知步驟",
"parse_error": "解析伺服器訊息失敗",
"speed": "速度",
"remaining": "剩餘時間",
"java_error_title": "Java 未找到",
"java_error_desc": "未在系統變數中找到 Java請安裝 Java否則開伺服器模式將無法使用\n\n建議\n1. 下載並安裝 Java 17 或更高版本\n2. 設定 Java 環境變數\n3. 重新啟動應用程式",
"network_error_title": "網路錯誤",
"network_error_desc": "網路連線出現問題\n錯誤訊息{error}",
"file_error_title": "檔案錯誤",
"file_error_desc": "檔案操作出現問題\n錯誤訊息{error}",
"memory_error_title": "記憶體錯誤",
"memory_error_desc": "記憶體不足\n錯誤訊息{error}",
"unknown_error_title": "未知錯誤",
"unknown_error_desc": "發生未知錯誤,請聯絡技術支援",
"ws_error_title": "WebSocket 連線失敗",
"ws_error_desc": "無法建立與後端的 WebSocket 連線",
"suggestions": "建議解決方案",
"suggestion_check_network": "檢查網路連線是否正常",
"suggestion_check_firewall": "檢查防火牆設定",
"suggestion_retry": "稍後重試",
"suggestion_check_disk_space": "檢查磁碟空間是否充足",
"suggestion_check_permission": "檢查檔案權限設定",
"suggestion_check_file_format": "確認檔案格式正確",
"suggestion_increase_memory": "增加分配給應用程式的記憶體",
"suggestion_close_other_apps": "關閉其他佔用記憶體的應用程式",
"suggestion_restart_application": "重新啟動應用程式",
"suggestion_check_backend": "檢查後端服務是否正常執行",
"suggestion_check_logs": "查看日誌檔案獲取更多資訊",
"suggestion_check_port": "檢查連接埠 37019 是否被佔用",
"suggestion_contact_support": "聯絡技術支援獲取協助",
"template_select_title": "選擇伺服器模板",
"template_select_desc": "為您的伺服器選擇一個模板,使用模板將跳過伺服器安裝,直接複製模板檔案到伺服器目錄",
"template_official_loader": "無",
"template_official_loader_desc": "不使用任何模板,進行標準的伺服器安裝流程",
"template_name": "模板名稱",
"template_description": "描述",
"template_author": "作者",
"template_version": "版本",
"template_selected": "已選擇模板",
"template_select_button": "選擇模板",
"template_loading": "載入模板列表中...",
"template_load_failed": "載入模板列表失敗",
"template_apply_success": "模板應用成功",
"template_apply_failed": "模板應用失敗"
},
"template": {
"title": "模板管理",
"description": "管理您的伺服器模板,建立、查看和刪除模板",
"create_button": "建立模板",
"create_title": "建立新模板",
"name": "模板名稱",
"name_required": "模板名稱不能為空",
"name_placeholder": "請輸入模板名稱",
"version": "版本",
"version_placeholder": "請輸入版本號",
"description": "描述",
"description_placeholder": "請輸入模板描述",
"author": "作者",
"author_placeholder": "請輸入作者名稱",
"delete_button": "刪除",
"delete_title": "刪除模板",
"delete_confirm": "確定要刪除模板 \"{name}\" 嗎?",
"delete_warning": "此操作將永久刪除該模板及其所有檔案,無法恢復。",
"delete_success": "模板刪除成功",
"delete_failed": "模板刪除失敗",
"create_success": "模板建立成功",
"create_failed": "模板建立失敗",
"open_folder": "打開文件夾",
"open_folder_success": "已打開模板文件夾",
"open_folder_failed": "打開文件夾失敗",
"edit_button": "編輯",
"edit_title": "編輯模板",
"update_success": "模板更新成功",
"update_failed": "模板更新失敗",
"empty": "還沒有模板",
"empty_hint": "點擊上方按鈕建立你的第一個模板"
},
"setting": {
"title": "DeEarthX 設定",
"subtitle": "讓 DeEarthX V3 更適合你!",
"category_filter": "模組篩選設定",
"category_mirror": "下載來源設定",
"category_system": "系統管理設定",
"filter_hashes_name": "雜湊過濾",
"filter_hashes_desc": "過濾不必要的客戶端模組(雜湊過濾法)",
"filter_dexpub_name": "Galaxy Square 過濾",
"filter_dexpub_desc": "過濾 Galaxy Square 平台中記錄的客戶端檔案",
"filter_modrinth_name": "Modrinth API 過濾",
"filter_modrinth_desc": "透過 Modrinth API 檢查模組的客戶端/伺服器相容性",
"filter_mixins_name": "Mixin 過濾",
"filter_mixins_desc": "過濾 Client Mixin 相關檔案",
"mirror_mcimirror_name": "MCIM 鏡像來源",
"mirror_mcimirror_desc": "使用 MCIM 鏡像來源加速下載",
"mirror_bmclapi_name": "BMCLAPI 鏡像來源",
"mirror_bmclapi_desc": "使用 BMCLAPI 鏡像來源加速下載",
"system_oaf_name": "操作完成後開啟目錄",
"system_oaf_desc": "伺服器製作完成後自動開啟目錄",
"system_autozip_name": "自動打包成 zip",
"system_autozip_desc": "伺服器製作完成後自動打包成 zip開伺服器模式不打包",
"switch_on": "開",
"switch_off": "關",
"language_title": "語言設定",
"language_desc": "選擇介面顯示語言",
"language_chinese": "簡體中文",
"language_english": "English",
"config_saved": "設定已儲存",
"config_load_failed": "載入設定失敗",
"config_save_failed": "儲存設定失敗",
"export_config": "匯出設定",
"import_config": "匯入設定",
"config_exported": "設定匯出成功",
"config_export_failed": "設定匯出失敗",
"config_imported": "設定匯入成功",
"config_import_failed": "設定匯入失敗",
"config_invalid_format": "設定檔案格式無效"
},
"about": {
"title": "關於 DeEarthX",
"subtitle": "專業的 Minecraft 整合包伺服器製作工具",
"about_software": "關於軟體",
"current_version": "目前版本:",
"build_time": "建置時間:",
"author": "作者:",
"development_team": "開發團隊",
"author_tianpao": "天跑",
"contribution_author": "作者",
"dev2_xcc": "XCC",
"contribution_dev2": "2號開發改良與優化",
"contributor_bangbang93": "bangbang93",
"contribution_bangbang93": "BMCLAPI 鏡像",
"contributor_z0z0r4": "z0z0r4",
"contribution_z0z0r4": "MCIM 鏡像",
"sponsor": "贊助商",
"sponsor_elfidc": "億訊雲",
"sponsor_type_gold": "金牌贊助",
"version_file_read_failed": "版本檔案讀取失敗"
},
"galaxy": {
"title": "Galaxy Square",
"subtitle": "讓所有模組都在這裡發光",
"mod_submit_title": "模組提交",
"mod_type_label": "模組類型",
"mod_type_client": "客戶端模組",
"mod_type_server": "伺服器模組",
"modid_label": "Modid",
"modid_placeholder": "請輸入 Modid多個用逗號分隔或上傳檔案自動獲取",
"modid_count": "目前已新增 {count} 個 Modid",
"upload_file_label": "上傳檔案",
"upload_file_hint": "點擊或拖曳檔案到此區域上傳",
"upload_file_support": "支援 .jar 格式檔案,可多選",
"file_selected": "已選擇 {count} 個檔案",
"start_upload": "開始上傳",
"uploading": "上傳中...",
"submit": "提交 {type} 模組",
"submitting": "提交中...",
"submit_confirm_title": "確認提交",
"submit_confirm_content": "確定要提交 {count} 個 {type} 模組嗎?",
"please_select_file": "請先選擇檔案",
"upload_success": "成功上傳 {count} 個檔案",
"data_format_error": "傳回資料格式錯誤",
"upload_failed": "上傳失敗",
"upload_error": "上傳出錯,請重試",
"submit_success": "{type} 模組提交成功",
"submit_failed": "提交失敗",
"submit_error": "提交出錯,請重試"
},
"message": {
"backend_running": "DeEarthX.Core 已在執行",
"backend_started": "DeEarthX.Core 啟動成功",
"backend_port_occupied": "37019 連接埠被其他應用程式佔用!",
"backend_start_failed": "DeEarthX.Core 啟動失敗,請檢查 37019 連接埠是否被佔用!(已重試 {count} 次)",
"backend_restart": "DeEarthX.Core 重新啟動!",
"retry_start": "啟動失敗,正在重試 ({current}/{max})...",
"config_load_error": "載入設定失敗"
}
}

39
front/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "dex-v3-ui",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"build:with-error-handling": "node build-with-error-handling.js",
"preview": "vite preview",
"tauri": "tauri",
"tauri-dev": "tauri dev --no-watch",
"tauri-build": "tauri build"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@tailwindcss/vite": "^4.2.1",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "^2.4.2",
"ant-design-vue": "^4.2.6",
"axios": "^1.13.6",
"tailwindcss": "^4.2.1",
"vue": "^3.5.29",
"vue-i18n": "^11.2.8",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@tauri-apps/cli": "^2.10.1",
"@tauri-apps/plugin-shell": "^2.3.1",
"@vitejs/plugin-vue": "^5.2.4",
"typescript": "~5.6.3",
"vite": "^6.4.1",
"vue-tsc": "^2.2.12"
}
}

BIN
front/public/bb93.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
front/public/dex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 MiB

1
front/public/elfidc.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

6
front/public/tauri.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
front/public/tianpao.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,5 @@
{
"version": "3.0.35",
"buildTime": "2026-03-10",
"author": "DeEarthX Team"
}

1
front/public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
front/public/xcc.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
front/public/z0z0r4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

7
front/src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

View File

@@ -0,0 +1,3 @@
capabilities/
target/debug/
target/release/

5
front/src-tauri/2 Normal file
View File

@@ -0,0 +1,5 @@
added 1 package in 2s
16 packages are looking for funding
run `npm fund` for details

5483
front/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
[package]
name = "dex-v3-ui"
version = "1.0.0"
description = "DeEarthX V3 - Minecraft整合包服务端制作工具"
authors = ["Tianpao"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "dex_v3_ui_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
open = "5.3.2"
tauri-plugin-store = "2"
tauri-plugin-shell = "2"
tauri-plugin-notification = "2"
tauri-plugin-dialog = "2"

3
front/src-tauri/build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"permissions": [
"core:default",
"opener:default",
"store:default",
"shell:default",
"shell:allow-execute",
"shell:allow-spawn",
"shell:allow-kill",
"shell:allow-open",
{
"identifier": "shell:allow-spawn",
"allow": [
{
"name": "core",
"cmd": "core.exe",
"args": true
}
]
},
"notification:default",
"dialog:default"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,21 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[allow(unused_imports)]
use tauri_plugin_opener::OpenerExt;
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
dex_v3_ui_lib::run()
}

View File

@@ -0,0 +1,55 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "DeEarthX-V3",
"version": "1.0.0",
"identifier": "top.tianpao.dex-v3-ui",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:9888",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "DeEarthX V3",
"width": 1024,
"height": 600,
"minWidth": 800,
"minHeight": 500,
"dragDropEnabled": false,
"fullscreen": false,
"resizable": true,
"center": true
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"externalBin": ["binaries/core"],
"windows": {
"webviewInstallMode": {
"type": "downloadBootstrapper"
},
"nsis": {
"languages": ["SimpChinese","English"],
"displayLanguageSelector": true,
"installMode": "perMachine"
},
"wix": {
"language": "zh-CN"
}
}
}
}

403
front/src/App.vue Normal file
View File

@@ -0,0 +1,403 @@
<script lang="ts" setup>
import { h, provide, ref, onMounted, computed } from 'vue';
import { MenuProps, message } from 'ant-design-vue';
import { SettingOutlined, UploadOutlined, UserOutlined, WindowsOutlined, LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined, FileSearchOutlined, FolderOutlined } from '@ant-design/icons-vue';
import { useRouter, useRoute } from 'vue-router';
import { Command } from '@tauri-apps/plugin-shell';
import { useI18n } from 'vue-i18n';
const router = useRouter();
const route = useRoute();
let killCoreProcess: (() => void) | null = null;
const { t } = useI18n();
// 版本号相关
const version = ref<string>('V3');
// 加载版本号
async function loadVersion() {
try {
console.log('开始加载版本号...');
const response = await fetch('/version.json');
console.log('version.json 响应状态:', response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('版本号数据:', data);
version.value = `V${data.version}`;
console.log('设置版本号为:', version.value);
} catch (error) {
console.error('加载版本号失败:', error);
version.value = 'V3';
}
}
// 后端连接状态相关
const backendStatus = ref<'loading' | 'success' | 'error'>('loading');
const backendErrorInfo = ref<string>('');
const retryCount = ref<number>(0);
const maxRetries = 5;
// 检测端口是否被正确的后端占用
async function checkPortOccupied(): Promise<'correct_backend' | 'wrong_app' | 'free'> {
try {
const response = await fetch("http://localhost:37019/config/get", {
method: "GET",
signal: AbortSignal.timeout(1000)
});
if (response.ok) {
const config = await response.json();
// 检查是否包含 DeEarthX 后端的特征字段mirror、filter 等)
if (config.mirror !== undefined || config.filter !== undefined) {
// 端口被正确的后端占用
return 'correct_backend';
} else {
// 端口被其他应用占用
return 'wrong_app';
}
} else {
return 'free';
}
} catch (error) {
// 连接失败,端口可能是空闲的
return 'free';
}
}
// 启动后端核心服务
async function runCoreProcess() {
// 先检测端口状态
const portStatus = await checkPortOccupied();
if (portStatus === 'correct_backend') {
// 端口已经被正确的后端占用,直接使用
backendStatus.value = 'success';
backendErrorInfo.value = '';
message.success(t('message.backend_running'));
return;
}
if (portStatus === 'wrong_app') {
// 端口被其他应用占用
backendStatus.value = 'error';
backendErrorInfo.value = t('message.backend_port_occupied');
message.error(t('message.backend_port_occupied'));
return;
}
// 端口空闲,尝试启动后端
backendStatus.value = 'loading';
Command.create("core").spawn()
.then((e) => {
console.log("DeEarthX V3 Core");
killCoreProcess = e.kill;
// 等待后端启动并检查状态
setTimeout(async () => {
try {
const response = await fetch("http://localhost:37019/", { method: "GET" });
if (response.ok) {
backendStatus.value = 'success';
backendErrorInfo.value = '';
message.success(t('message.backend_started'));
} else {
backendStatus.value = 'error';
backendErrorInfo.value = t('common.status_error');
router.push('/error');
}
} catch (error) {
console.error("后端连接失败:", error);
backendStatus.value = 'error';
backendErrorInfo.value = t('common.status_error');
router.push('/error');
}
}, 3000); // 等待3秒让后端启动
})
.catch((error) => {
console.error(error);
retryCount.value++;
if (retryCount.value <= maxRetries) {
message.info(t('message.retry_start', { current: retryCount.value, max: maxRetries }));
setTimeout(() => {
runCoreProcess();
}, 2000);
} else {
backendStatus.value = 'error';
backendErrorInfo.value = t('message.backend_start_failed', { count: maxRetries });
message.error(t('message.backend_start_failed', { count: maxRetries }));
}
});
}
// 组件挂载时启动后端
onMounted(async () => {
loadVersion();
runCoreProcess();
});
provide("killCoreProcess", () => {
if (killCoreProcess && typeof killCoreProcess === 'function') {
killCoreProcess();
killCoreProcess = null;
message.info(t('message.backend_restart'));
runCoreProcess();
}
});
// 导航菜单配置
const selectedKeys = ref<(string | number)[]>(['main']);
// 监听路由变化,更新选中菜单
router.beforeEach((to, _from, next) => {
const routeToKey: Record<string, string> = {
'/': 'main',
'/setting': 'setting',
'/about': 'about',
'/error': 'main',
'/galaxy': 'galaxy',
'/deearth': 'deearth',
'/template': 'template'
};
selectedKeys.value[0] = routeToKey[to.path] || 'main';
next();
});
// 菜单项配置(使用计算属性使其响应语言变化)
const menuItems = computed<MenuProps['items']>(() => {
return [
{
key: 'main',
icon: h(WindowsOutlined),
label: t('menu.home'),
title: t('menu.home'),
},
{
key: 'deearth',
icon: h(FileSearchOutlined),
label: t('menu.deearth'),
title: t('menu.deearth'),
},
{
key: 'galaxy',
icon: h(UploadOutlined),
label: t('menu.galaxy'),
title: t('menu.galaxy'),
},
{
key: 'template',
icon: h(FolderOutlined),
label: t('menu.template'),
title: t('menu.template'),
},
{
key: 'setting',
icon: h(SettingOutlined),
label: t('menu.setting'),
title: t('menu.setting'),
},
{
key: 'about',
icon: h(UserOutlined),
label: t('menu.about'),
title: t('menu.about'),
}
];
});
// 菜单点击事件处理
const handleMenuClick: MenuProps['onClick'] = (e) => {
selectedKeys.value[0] = e.key;
const routeMap: Record<string, string> = {
main: '/',
deearth: '/deearth',
setting: '/setting',
about: '/about',
galaxy: '/galaxy',
template: '/template'
};
const route = routeMap[e.key] || '/';
router.push(route);
};
// 主题配置
const theme = ref({
token: {
colorPrimary: '#67eac3',
borderRadius: 8,
},
components: {
Menu: {
itemActiveBg: '#e8fff5',
itemSelectedBg: '#e8fff5',
itemSelectedColor: '#10b981',
}
}
});
</script>
<template>
<a-config-provider :theme="theme">
<div class="tw:h-screen tw:w-screen tw:flex tw:flex-col tw:overflow-hidden">
<!-- 顶部导航栏 -->
<a-page-header
class="tw:h-14 tw:px-6 tw:flex tw:items-center tw:bg-white tw:shadow-sm tw:z-10 tw:transition-all tw:duration-300"
style="border: none;"
>
<!-- <template #extra>
<a-button @click="openAuthorBilibili">作者B站</a-button>
</template> -->
<!-- 后端状态图标 -->
<template #title>
<div class="tw:flex tw:items-center tw:gap-3">
<span>
<span style="color: #000000; font-weight: 500;">{{ t('common.app_name') }}</span>
<span style="color: #888888; font-size: 12px; margin-left: 5px;">{{ version }}</span>
</span>
<span
class="tw:flex tw:items-center tw:gap-2"
:title="backendErrorInfo || t('message.backend_running')"
>
<LoadingOutlined v-if="backendStatus === 'loading'" style="color: #1890ff; font-size: 18px;" />
<CheckCircleOutlined v-else-if="backendStatus === 'success'" style="color: #52c41a; font-size: 18px;" />
<CloseCircleOutlined v-else style="color: #ff4d4f; font-size: 18px;" />
<span class="tw:text-xs tw:ml-1"
:style="{
color: backendStatus === 'loading' ? '#1890ff' :
backendStatus === 'success' ? '#52c41a' : '#ff4d4f'
}">
{{ backendStatus === 'loading' ? t('common.status_loading') :
backendStatus === 'success' ? t('common.status_success') : t('common.status_error') }}
</span>
</span>
</div>
</template>
</a-page-header>
<!-- 主体内容区域 -->
<div class="tw:flex tw:flex-1 tw:overflow-hidden">
<!-- 侧边菜单 -->
<a-menu
id="menu"
class="tw:shadow-lg tw:z-20"
style="width: 180px; flex-shrink: 0;"
:selectedKeys="selectedKeys"
mode="inline"
:items="menuItems"
@click="handleMenuClick"
/>
<!-- 内容区域 - 带过渡动画 -->
<div class="tw:flex-1 tw:overflow-hidden tw:relative tw:bg-gradient-to-br tw:from-slate-50 tw:via-blue-50 tw:to-indigo-50">
<router-view v-slot="{ Component }">
<transition
name="fade-slide"
mode="out-in"
appear
>
<component :is="Component" :key="route.path" class="tw:w-full tw:h-full tw:absolute tw:top-0 tw:left-0" />
</transition>
</router-view>
</div>
</div>
</div>
</a-config-provider>
</template>
<style>
/* 禁止选择文本的样式 */
h1,
li,
p,
span {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* 禁止拖拽图片 */
img {
-webkit-user-drag: none;
-moz-user-drag: none;
-ms-user-drag: none;
}
/* 页面切换过渡动画 - 淡入淡出 + 滑动 */
.fade-slide-enter-active {
animation: fadeSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-slide-leave-active {
animation: fadeSlideOut 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes fadeSlideIn {
0% {
opacity: 0;
transform: translateX(20px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeSlideOut {
0% {
opacity: 1;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(-20px);
}
}
/* 菜单项悬停效果优化 */
#menu .ant-menu-item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 8px;
}
#menu .ant-menu-item:hover {
transform: translateX(4px);
background: #f0fdf9;
}
#menu .ant-menu-item-selected {
background: linear-gradient(135deg, #d1fae5 0%, #e8fff5 100%);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.15);
}
#menu .ant-menu-item-selected .anticon {
color: #10b981;
}
/* 滚动条美化 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
border-radius: 4px;
transition: all 0.3s ease;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #64748b 0%, #475569 100%);
}
</style>

1
front/src/assets/vue.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,44 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import type { SelectProps } from 'ant-design-vue/es/vc-select';
const { t } = useI18n();
interface Props {
modelValue: string;
javaAvailable: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits<{
'update:modelValue': [value: string];
'select': [value: string];
}>();
const modeOptions = computed<SelectProps['options']>(() => {
return [
{ label: t('home.mode_server'), value: 'server', disabled: !props.javaAvailable },
{ label: t('home.mode_upload'), value: 'upload', disabled: false }
];
});
function handleSelect(value: string) {
emit('update:modelValue', value);
emit('select', value);
}
</script>
<template>
<div>
<h2 class="tw:text-sm tw:font-semibold tw:text-gray-700 tw:mb-3">
{{ t('home.mode_title') }}
</h2>
<a-select
:value="modelValue"
:options="modeOptions"
@select="handleSelect"
class="tw:w-full"
/>
</div>
</template>

View File

@@ -0,0 +1,147 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface ProgressStatus {
status: 'active' | 'success' | 'exception' | 'normal';
percent: number;
display: boolean;
uploadedSize?: number;
totalSize?: number;
speed?: number;
remainingTime?: number;
}
const uploadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const unzipProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const downloadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const serverInstallProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const filterModsProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const serverInstallInfo = ref({
modpackName: '',
minecraftVersion: '',
loaderType: '',
loaderVersion: '',
currentStep: '',
stepIndex: 0,
totalSteps: 0,
message: '',
status: 'idle' as 'idle' | 'installing' | 'completed' | 'error',
error: '',
installPath: '',
duration: 0
});
const filterModsInfo = ref({
totalMods: 0,
currentMod: 0,
modName: '',
filteredCount: 0,
movedCount: 0,
status: 'idle' as 'idle' | 'filtering' | 'completed' | 'error',
error: '',
duration: 0
});
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
function formatTime(seconds: number): string {
if (seconds < 60) return `${Math.round(seconds)}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
}
defineExpose({
uploadProgress,
unzipProgress,
downloadProgress,
serverInstallProgress,
filterModsProgress,
serverInstallInfo,
filterModsInfo
});
watch(() => window.progressData, (newData) => {
if (newData) {
if (newData.uploadProgress) uploadProgress.value = newData.uploadProgress;
if (newData.unzipProgress) unzipProgress.value = newData.unzipProgress;
if (newData.downloadProgress) downloadProgress.value = newData.downloadProgress;
if (newData.serverInstallProgress) serverInstallProgress.value = newData.serverInstallProgress;
if (newData.filterModsProgress) filterModsProgress.value = newData.filterModsProgress;
if (newData.serverInstallInfo) serverInstallInfo.value = newData.serverInstallInfo;
if (newData.filterModsInfo) filterModsInfo.value = newData.filterModsInfo;
}
}, { deep: true });
</script>
<template>
<div class="tw:absolute tw:right-2 tw:bottom-32 tw:h-80 tw:w-64 tw:rounded-xl tw:overflow-y-auto">
<a-card :title="t('home.progress_title')" :bordered="true" class="tw:h-full">
<div v-if="uploadProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.upload_progress') }}</h1>
<a-progress :percent="uploadProgress.percent" :status="uploadProgress.status" size="small" />
<div v-if="uploadProgress.totalSize" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ formatFileSize(uploadProgress.uploadedSize || 0) }} / {{ formatFileSize(uploadProgress.totalSize) }}
<span v-if="uploadProgress.speed" class="tw:ml-2">
{{ t('home.speed') }}: {{ formatFileSize(uploadProgress.speed) }}/s
</span>
<span v-if="uploadProgress.remainingTime" class="tw:ml-2">
{{ t('home.remaining') }}: {{ formatTime(uploadProgress.remainingTime) }}
</span>
</div>
</div>
<div v-if="unzipProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.unzip_progress') }}</h1>
<a-progress :percent="unzipProgress.percent" :status="unzipProgress.status" size="small" />
</div>
<div v-if="downloadProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.download_progress') }}</h1>
<a-progress :percent="downloadProgress.percent" :status="downloadProgress.status" size="small" />
</div>
<div v-if="serverInstallProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.server_install_progress') }}</h1>
<a-progress :percent="serverInstallProgress.percent" :status="serverInstallProgress.status" size="small" />
<div v-if="serverInstallInfo.currentStep" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ t('home.server_install_step') }}: {{ serverInstallInfo.currentStep }}
<span v-if="serverInstallInfo.totalSteps > 0">
({{ serverInstallInfo.stepIndex }}/{{ serverInstallInfo.totalSteps }})
</span>
</div>
<div v-if="serverInstallInfo.message" class="tw:text-xs tw:text-gray-600 tw:mt-1 tw:break-words">
{{ t('home.server_install_message') }}: {{ serverInstallInfo.message }}
</div>
<div v-if="serverInstallInfo.status === 'completed'" class="tw:text-xs tw:text-green-600 tw:mt-1">
{{ t('home.server_install_completed') }} {{ t('home.server_install_duration') }}: {{ (serverInstallInfo.duration / 1000).toFixed(2) }}s
</div>
<div v-if="serverInstallInfo.status === 'error'" class="tw:text-xs tw:text-red-600 tw:mt-1 tw:break-words">
{{ t('home.server_install_error') }}: {{ serverInstallInfo.error }}
</div>
</div>
<div v-if="filterModsProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.filter_mods_progress') }}</h1>
<a-progress :percent="filterModsProgress.percent" :status="filterModsProgress.status" size="small" />
<div v-if="filterModsInfo.totalMods > 0" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ t('home.filter_mods_total') }}: {{ filterModsInfo.totalMods }}
</div>
<div v-if="filterModsInfo.modName" class="tw:text-xs tw:text-gray-600 tw:mt-1 tw:break-words">
{{ t('home.filter_mods_current') }}: {{ filterModsInfo.modName }}
</div>
<div v-if="filterModsInfo.status === 'completed'" class="tw:text-xs tw:text-green-600 tw:mt-1">
{{ t('home.filter_mods_completed', { filtered: filterModsInfo.filteredCount, moved: filterModsInfo.movedCount }) }}
</div>
<div v-if="filterModsInfo.status === 'error'" class="tw:text-xs tw:text-red-600 tw:mt-1 tw:break-words">
{{ t('home.filter_mods_error') }}: {{ filterModsInfo.error }}
</div>
</div>
</a-card>
</div>
</template>

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import type { StepsProps } from 'ant-design-vue';
interface Props {
current: number;
items: Required<StepsProps>['items'];
}
defineProps<Props>();
</script>
<template>
<div class="tw:fixed tw:bottom-2 tw:left-1/2 tw:-translate-x-1/2 tw:w-[65%] tw:h-20 tw:flex tw:justify-center tw:items-center tw:text-sm tw:bg-white tw:rounded-xl tw:shadow-lg tw:px-4 tw:ml-10">
<a-steps :current="current" :items="items" size="small" />
</div>
</template>

View File

@@ -0,0 +1,475 @@
<script lang="ts" setup>
import { ref, inject, watch, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { message, notification } from 'ant-design-vue';
import { sendNotification } from '@tauri-apps/plugin-notification';
const { t } = useI18n();
interface Props {
file: File | undefined;
mode: string;
showSteps: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits<{
'step-change': [step: number];
'reset': [];
}>();
const currentStep = ref(0);
const startTime = ref<number>(0);
const javaAvailable = ref(true);
const abortController = ref<AbortController | null>(null);
const ws = ref<WebSocket | null>(null);
interface ProgressStatus {
status: 'active' | 'success' | 'exception' | 'normal';
percent: number;
display: boolean;
uploadedSize?: number;
totalSize?: number;
speed?: number;
remainingTime?: number;
}
const uploadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const unzipProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const downloadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const serverInstallProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const filterModsProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const serverInstallInfo = ref({
modpackName: '',
minecraftVersion: '',
loaderType: '',
loaderVersion: '',
currentStep: '',
stepIndex: 0,
totalSteps: 0,
message: '',
status: 'idle' as 'idle' | 'installing' | 'completed' | 'error',
error: '',
installPath: '',
duration: 0
});
const filterModsInfo = ref({
totalMods: 0,
currentMod: 0,
modName: '',
filteredCount: 0,
movedCount: 0,
status: 'idle' as 'idle' | 'filtering' | 'completed' | 'error',
error: '',
duration: 0
});
function resetState() {
// 取消当前请求
if (abortController.value) {
abortController.value.abort();
abortController.value = null;
}
// 关闭WebSocket连接
if (ws.value) {
ws.value.close();
ws.value = null;
}
uploadProgress.value = { status: 'active', percent: 0, display: false };
unzipProgress.value = { status: 'active', percent: 0, display: true };
downloadProgress.value = { status: 'active', percent: 0, display: true };
serverInstallProgress.value = { status: 'active', percent: 0, display: false };
filterModsProgress.value = { status: 'active', percent: 0, display: false };
currentStep.value = 0;
const killCoreProcess = inject("killCoreProcess");
if (killCoreProcess && typeof killCoreProcess === 'function') {
killCoreProcess();
}
}
watch(() => props.showSteps, (newVal) => {
if (newVal && props.file) {
runDeEarthX(props.file);
}
});
async function runDeEarthX(file: File) {
message.success(t('home.start_production'));
const formData = new FormData();
formData.append('file', file);
try {
message.loading(t('home.task_preparing'));
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const url = `http://${apiHost}:${apiPort}/start?mode=${props.mode}`;
uploadProgress.value = { status: 'active', percent: 0, display: true };
startTime.value = Date.now();
// 创建新的AbortController
abortController.value = new AbortController();
const response = await fetch(url, {
method: 'POST',
body: formData,
signal: abortController.value.signal
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
uploadProgress.value.status = 'success';
uploadProgress.value.percent = 100;
setTimeout(() => {
uploadProgress.value.display = false;
}, 2000);
message.success(t('home.task_connecting'));
setupWebSocket();
} catch (error: any) {
if (error.name !== 'AbortError') {
console.error('请求失败:', error);
message.error(t('home.request_failed'));
uploadProgress.value.status = 'exception';
}
resetState();
}
}
function setupWebSocket() {
message.loading(t('home.ws_connecting'));
const wsHost = import.meta.env.VITE_WS_HOST || 'localhost';
const wsPort = import.meta.env.VITE_WS_PORT || '37019';
ws.value = new WebSocket(`ws://${wsHost}:${wsPort}/`);
ws.value.addEventListener('open', () => {
message.success(t('home.ws_connected'));
});
ws.value.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'error') {
handleError(data.message);
} else if (data.type === 'info') {
message.info(data.message);
} else if (data.status) {
switch (data.status) {
case 'error':
handleError(data.result);
break;
case 'changed':
currentStep.value++;
emit('step-change', currentStep.value);
break;
case 'unzip':
updateUnzipProgress(data.result);
break;
case 'downloading':
updateDownloadProgress(data.result);
break;
case 'finish':
handleFinish(data.result);
break;
case 'server_install_start':
handleServerInstallStart(data.result);
break;
case 'server_install_step':
handleServerInstallStep(data.result);
break;
case 'server_install_progress':
handleServerInstallProgress(data.result);
break;
case 'server_install_complete':
handleServerInstallComplete(data.result);
break;
case 'server_install_error':
handleServerInstallError(data.result);
break;
case 'filter_mods_start':
handleFilterModsStart(data.result);
break;
case 'filter_mods_progress':
handleFilterModsProgress(data.result);
break;
case 'filter_mods_complete':
handleFilterModsComplete(data.result);
break;
case 'filter_mods_error':
handleFilterModsError(data.result);
break;
}
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
notification.error({ message: t('common.error'), description: t('home.parse_error') });
}
});
ws.value.addEventListener('error', () => {
notification.error({
message: t('home.ws_error_title'),
description: `${t('home.ws_error_desc')}\n\n${t('home.suggestions')}:\n1. ${t('home.suggestion_check_backend')}\n2. ${t('home.suggestion_check_port')}\n3. ${t('home.suggestion_restart_application')}`,
duration: 0
});
resetState();
});
ws.value.addEventListener('close', () => {
console.log('WebSocket连接关闭');
ws.value = null;
});
}
// 组件卸载时清理资源
onUnmounted(() => {
resetState();
});
function handleError(result: any) {
if (result === 'jini') {
javaAvailable.value = false;
notification.error({
message: t('home.java_error_title'),
description: t('home.java_error_desc'),
duration: 0
});
} else if (typeof result === 'string') {
let errorTitle = t('home.backend_error');
let errorDesc = t('home.backend_error_desc', { error: result });
let suggestions: string[] = [];
if (result.includes('network') || result.includes('connection') || result.includes('timeout')) {
errorTitle = t('home.network_error_title');
errorDesc = t('home.network_error_desc', { error: result });
suggestions = [
t('home.suggestion_check_network'),
t('home.suggestion_check_firewall'),
t('home.suggestion_retry')
];
} else if (result.includes('file') || result.includes('permission') || result.includes('disk')) {
errorTitle = t('home.file_error_title');
errorDesc = t('home.file_error_desc', { error: result });
suggestions = [
t('home.suggestion_check_disk_space'),
t('home.suggestion_check_permission'),
t('home.suggestion_check_file_format')
];
} else if (result.includes('memory') || result.includes('out of memory') || result.includes('heap')) {
errorTitle = t('home.memory_error_title');
errorDesc = t('home.memory_error_desc', { error: result });
suggestions = [
t('home.suggestion_increase_memory'),
t('home.suggestion_close_other_apps'),
t('home.suggestion_restart_application')
];
} else {
suggestions = [
t('home.suggestion_check_backend'),
t('home.suggestion_check_logs'),
t('home.suggestion_contact_support')
];
}
const fullDescription = `${errorDesc}\n\n${t('home.suggestions')}:\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`;
notification.error({
message: errorTitle,
description: fullDescription,
duration: 0
});
resetState();
} else {
notification.error({
message: t('home.unknown_error_title'),
description: t('home.unknown_error_desc'),
duration: 0
});
resetState();
}
}
function updateUnzipProgress(result: { current: number; total: number }) {
unzipProgress.value.percent = Math.round((result.current / result.total) * 100);
if (result.current === result.total) {
unzipProgress.value.status = 'success';
setTimeout(() => {
unzipProgress.value.display = false;
}, 2000);
}
updateWindowProgress();
}
function updateDownloadProgress(result: { index: number; total: number }) {
downloadProgress.value.percent = Math.round((result.index / result.total) * 100);
if (downloadProgress.value.percent === 100) {
downloadProgress.value.status = 'success';
setTimeout(() => {
downloadProgress.value.display = false;
}, 2000);
}
updateWindowProgress();
}
function updateWindowProgress() {
(window as any).progressData = {
uploadProgress: uploadProgress.value,
unzipProgress: unzipProgress.value,
downloadProgress: downloadProgress.value,
serverInstallProgress: serverInstallProgress.value,
filterModsProgress: filterModsProgress.value,
serverInstallInfo: serverInstallInfo.value,
filterModsInfo: filterModsInfo.value
};
}
function handleFinish(result: number) {
const timeSpent = Math.round(result / 1000);
currentStep.value++;
emit('step-change', currentStep.value);
message.success(t('home.production_complete', { time: timeSpent }));
sendNotification({ title: t('common.app_name'), body: t('home.production_complete', { time: timeSpent }) });
setTimeout(() => {
emit('reset');
}, 8000);
}
function handleServerInstallStart(result: any) {
serverInstallInfo.value = {
modpackName: result.modpackName,
minecraftVersion: result.minecraftVersion,
loaderType: result.loaderType,
loaderVersion: result.loaderVersion,
currentStep: '',
stepIndex: 0,
totalSteps: 0,
message: 'Starting installation...',
status: 'installing',
error: '',
installPath: '',
duration: 0
};
serverInstallProgress.value = { status: 'active', percent: 0, display: true };
updateWindowProgress();
}
function handleServerInstallStep(result: any) {
serverInstallInfo.value.currentStep = result.step;
serverInstallInfo.value.stepIndex = result.stepIndex;
serverInstallInfo.value.totalSteps = result.totalSteps;
serverInstallInfo.value.message = result.message || result.step;
const overallProgress = (result.stepIndex / result.totalSteps) * 100;
serverInstallProgress.value.percent = Math.round(overallProgress);
updateWindowProgress();
}
function handleServerInstallProgress(result: any) {
serverInstallInfo.value.currentStep = result.step;
serverInstallInfo.value.message = result.message || result.step;
serverInstallProgress.value.percent = result.progress;
updateWindowProgress();
}
function handleServerInstallComplete(result: any) {
serverInstallInfo.value.status = 'completed';
serverInstallInfo.value.installPath = result.installPath;
serverInstallInfo.value.duration = result.duration;
serverInstallInfo.value.message = t('home.server_install_completed');
serverInstallProgress.value = { status: 'success', percent: 100, display: true };
currentStep.value++;
emit('step-change', currentStep.value);
const timeSpent = Math.round(result.duration / 1000);
message.success(t('home.server_install_completed') + ` ${t('home.server_install_duration')}: ${timeSpent}s`);
sendNotification({ title: t('common.app_name'), body: t('home.production_complete', { time: timeSpent }) });
setTimeout(() => {
serverInstallProgress.value.display = false;
}, 8000);
updateWindowProgress();
}
function handleServerInstallError(result: any) {
serverInstallInfo.value.status = 'error';
serverInstallInfo.value.error = result.error;
serverInstallInfo.value.message = result.error;
serverInstallProgress.value = { status: 'exception', percent: serverInstallProgress.value.percent, display: true };
notification.error({
message: t('home.server_install_error'),
description: result.error,
duration: 0
});
updateWindowProgress();
}
function handleFilterModsStart(result: any) {
filterModsInfo.value = {
totalMods: result.totalMods,
currentMod: 0,
modName: '',
filteredCount: 0,
movedCount: 0,
status: 'filtering',
error: '',
duration: 0
};
filterModsProgress.value = { status: 'active', percent: 0, display: true };
updateWindowProgress();
}
function handleFilterModsProgress(result: any) {
filterModsInfo.value.currentMod = result.current;
filterModsInfo.value.modName = result.modName;
const percent = Math.round((result.current / result.total) * 100);
filterModsProgress.value.percent = percent;
updateWindowProgress();
}
function handleFilterModsComplete(result: any) {
filterModsInfo.value.status = 'completed';
filterModsInfo.value.filteredCount = result.filteredCount;
filterModsInfo.value.movedCount = result.movedCount;
filterModsInfo.value.duration = result.duration;
filterModsProgress.value = { status: 'success', percent: 100, display: true };
const timeSpent = Math.round(result.duration / 1000);
message.success(t('home.filter_mods_completed', { filtered: result.filteredCount, moved: result.movedCount }) + ` ${t('home.server_install_duration')}: ${timeSpent}s`);
setTimeout(() => {
filterModsProgress.value.display = false;
}, 8000);
updateWindowProgress();
}
function handleFilterModsError(result: any) {
filterModsInfo.value.status = 'error';
filterModsInfo.value.error = result.error;
filterModsProgress.value = { status: 'exception', percent: filterModsProgress.value.percent, display: true };
notification.error({
message: t('home.filter_mods_error'),
description: result.error,
duration: 0
});
updateWindowProgress();
}
</script>
<template>
</template>

24
front/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_HOST?: string
readonly VITE_API_PORT?: string
readonly VITE_WS_HOST?: string
readonly VITE_WS_PORT?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
interface Window {
progressData?: {
uploadProgress?: any;
unzipProgress?: any;
downloadProgress?: any;
serverInstallProgress?: any;
filterModsProgress?: any;
serverInstallInfo?: any;
filterModsInfo?: any;
};
}

14
front/src/main.ts Normal file
View File

@@ -0,0 +1,14 @@
import { createApp } from "vue";
import App from "./App.vue";
import "./tailwind.css"
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import router from "./utils/router";
import i18n from "./utils/i18n";
const app = createApp(App);
app.use(router)
app.use(Antd)
app.use(i18n)
app.mount("#app");

1
front/src/tailwind.css Normal file
View File

@@ -0,0 +1 @@
@import "tailwindcss" prefix(tw);

30
front/src/utils/axios.ts Normal file
View File

@@ -0,0 +1,30 @@
import axios, { AxiosInstance, AxiosResponse } from 'axios';
const axiosInstance: AxiosInstance = axios.create({
baseURL: 'http://localhost:37019',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
axiosInstance.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
(error) => {
console.error('Axios error:', error);
return Promise.reject(error);
}
);
export default axiosInstance;

48
front/src/utils/i18n.ts Normal file
View File

@@ -0,0 +1,48 @@
import { createI18n } from 'vue-i18n';
import zhCn from '../../lang/zh_cn.json';
import zhHk from '../../lang/zh_hk.json';
import zhTw from '../../lang/zh_tw.json';
import enUs from '../../lang/en_us.json';
import jaJp from '../../lang/ja_jp.json';
import frFr from '../../lang/fr_fr.json';
import deDe from '../../lang/de_de.json';
import esEs from '../../lang/es_es.json';
export type Language = 'zh_cn' | 'zh_hk' | 'zh_tw' | 'en_us' | 'ja_jp' | 'fr_fr' | 'de_de' | 'es_es';
const messages = {
zh_cn: zhCn,
zh_hk: zhHk,
zh_tw: zhTw,
en_us: enUs,
ja_jp: jaJp,
fr_fr: frFr,
de_de: deDe,
es_es: esEs
};
const LANGUAGE_STORAGE_KEY = 'deearthx_language';
const savedLanguage = localStorage.getItem(LANGUAGE_STORAGE_KEY) as Language;
const defaultLocale = savedLanguage && messages[savedLanguage] ? savedLanguage : 'zh_cn';
const i18n = createI18n({
legacy: false,
locale: defaultLocale,
fallbackLocale: 'zh_cn',
messages,
globalInjection: true
});
export function setLanguage(lang: Language) {
if (i18n.global.locale.value !== lang) {
i18n.global.locale.value = lang;
localStorage.setItem(LANGUAGE_STORAGE_KEY, lang);
}
}
export function getLanguage(): Language {
return i18n.global.locale.value as Language;
}
export default i18n;

40
front/src/utils/router.ts Normal file
View File

@@ -0,0 +1,40 @@
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/",
component: () => import("../views/Main.vue"),
},
{
path: "/setting",
component: () => import("../views/SettingView.vue"),
meta: {
requiresConfigRefresh: true
}
},
{
path: "/about",
component: () => import("../views/AboutView.vue")
},
{
path: "/error",
component: () => import("../views/ErrorView.vue")
},
{
path: "/galaxy",
component: () => import("../views/GalaxyView.vue")
},
{
path: "/deearth",
component: () => import("../views/DeEarthView.vue")
},
{
path: "/template",
component: () => import("../views/TemplateView.vue")
}
]
})
export default router

View File

@@ -0,0 +1,258 @@
<script lang="ts" setup>
import { open } from "@tauri-apps/plugin-shell";
import { ref, onMounted, computed } from "vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface Sponsor {
id: string;
name: string;
imageUrl: string;
type: string;
url: string;
}
interface VersionInfo {
version: string;
buildTime: string;
author: string;
}
const sponsors = ref<Sponsor[]>([]);
const currentVersion = ref<string>(t('common.loading'));
const buildTime = ref<string>('');
const author = ref<string>('');
async function getVersionFromJson(): Promise<VersionInfo> {
try {
const response = await fetch('/version.json');
if (!response.ok) {
throw new Error(t('about.version_file_read_failed'));
}
return await response.json();
} catch (error) {
console.error('读取 version.json 失败:', error);
return {
version: '1.0.0',
buildTime: 'Unknown',
author: 'Tianpao'
};
}
}
async function getCurrentVersion() {
const versionInfo = await getVersionFromJson();
currentVersion.value = versionInfo.version;
buildTime.value = versionInfo.buildTime;
author.value = versionInfo.author;
}
const SPONSORS_JSON_URL = "https://bk.xcclyc.cn/upzzs.json";
async function fetchSponsors() {
try {
const response = await fetch(SPONSORS_JSON_URL);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
sponsors.value = data;
} catch (error) {
console.error("Failed to fetch sponsors:", error);
sponsors.value = [
{
id: "elfidc",
name: "亿讯云",
imageUrl: "./elfidc.svg",
type: t('about.sponsor_type_gold'),
url: "https://www.elfidc.com"
}
];
}
}
const thanksList = computed(() => {
return [
{
id: "user",
name: "天跑",
avatar: "./tianpao.jpg",
contribution: t('about.contribution_author'),
bilibiliUrl: "https://space.bilibili.com/1728953419"
},
{
id: "dev2",
name: "XCC",
avatar: "./xcc.jpg",
contribution: t('about.contribution_dev2'),
bilibiliUrl: "https://space.bilibili.com/3546586967706135"
},
{
id: "mirror",
name: "bangbang93",
avatar: "./bb93.jpg",
contribution: t('about.contribution_bangbang93')
},{
id: "mirror",
name: "z0z0r4",
avatar: "./z0z0r4.jpg",
contribution: t('about.contribution_z0z0r4')
}
];
});
async function contant(sponsor: Sponsor){
try {
await open(sponsor.url)
} catch (error) {
console.error("Failed to open sponsor URL:", error)
window.open(sponsor.url, '_blank')
}
}
async function openBilibili(url: string) {
try {
await open(url);
} catch (error) {
console.error("Failed to open Bilibili URL:", error)
window.open(url, '_blank')
}
}
onMounted(() => {
fetchSponsors();
getCurrentVersion();
});
</script>
<template>
<div class="tw:h-full tw:w-full tw:p-8 tw:bg-gradient-to-br tw:from-slate-50 tw:via-blue-50 tw:to-indigo-50 tw:overflow-auto">
<div class="tw:w-full tw:max-w-5xl tw:mx-auto tw:flex tw:flex-col tw:gap-8">
<div class="tw:text-center tw:animate-fade-in">
<h1 class="tw:text-3xl tw:font-bold tw:bg-gradient-to-r tw:from-emerald-500 tw:via-cyan-500 tw:to-blue-500 tw:bg-clip-text tw:text-transparent tw:mb-3">
{{ t('about.title') }}
</h1>
<p class="tw:text-gray-500 tw:text-lg">{{ t('about.subtitle') }}</p>
</div>
<div class="tw:bg-white tw:rounded-2xl tw:shadow-lg tw:p-8 tw:animate-fade-in-up">
<h2 class="tw:text-xl tw:font-bold tw:text-gray-800 tw:text-center tw:mb-6 tw:flex tw:items-center tw:justify-center tw:gap-3">
<span class="tw:text-2xl"></span>
<span>{{ t('about.about_software') }}</span>
</h2>
<div class="tw:flex tw:flex-col tw:items-center tw:gap-6">
<div class="tw:flex tw:flex-col tw:items-center tw:gap-3">
<div class="tw:flex tw:items-center tw:gap-3">
<span class="tw:text-gray-600 tw:text-base">{{ t('about.current_version') }}</span>
<span class="tw:text-2xl tw:font-bold tw:bg-gradient-to-r tw:from-emerald-500 tw:to-cyan-500 tw:bg-clip-text tw:text-transparent">
{{ currentVersion }}
</span>
</div>
</div>
<div class="tw:flex tw:flex-col tw:items-center tw:gap-2 tw:text-gray-500 tw:text-sm">
<div class="tw:flex tw:items-center tw:gap-2">
<span>{{ t('about.build_time') }}</span>
<span class="tw:font-medium">{{ buildTime }}</span>
</div>
<div class="tw:flex tw:items-center tw:gap-2">
<span>{{ t('about.author') }}</span>
<span class="tw:font-medium">{{ author }}</span>
</div>
</div>
</div>
</div>
<div class="tw:bg-white tw:rounded-2xl tw:shadow-lg tw:p-8 tw:animate-fade-in-up">
<h2 class="tw:text-xl tw:font-bold tw:text-gray-800 tw:text-center tw:mb-8 tw:flex tw:items-center tw:justify-center tw:gap-3">
<span class="tw:text-2xl"></span>
<span>{{ t('about.development_team') }}</span>
</h2>
<div class="tw:grid tw:grid-cols-2 md:tw:grid-cols-3 lg:tw:grid-cols-5 tw:gap-6 tw:justify-items-center">
<div
v-for="item in thanksList"
:key="item.id"
class="tw:flex tw:flex-col tw:items-center tw:w-36 tw:p-5 tw:bg-gradient-to-br tw:from-white tw:to-gray-50 tw:rounded-2xl tw:shadow-sm tw:transition-all duration-300 hover:shadow-xl hover:-translate-y-2 tw:border tw:border-gray-100 tw:group"
>
<div class="tw:w-20 tw:h-20 tw:bg-gradient-to-br tw:from-emerald-100 tw:to-cyan-100 tw:rounded-full tw:overflow-hidden tw:flex tw:items-center tw:justify-center tw:mb-4 tw:ring-2 tw:ring-emerald-200 tw:ring-offset-2 tw:group-hover:tw:ring-emerald-400 tw:transition-all duration-300">
<img class="tw:w-full tw:h-full tw:object-cover" :src="item.avatar" :alt="item.name">
</div>
<h3 class="tw:text-sm tw:font-bold tw:text-gray-800 tw:group-hover:tw:text-emerald-600 tw:transition-colors">{{ item.name }}</h3>
<p class="tw:text-xs tw:text-gray-500 tw:mt-2">{{ item.contribution }}</p>
<a-button
v-if="item.bilibiliUrl"
type="link"
size="small"
class="tw:text-xs tw:mt-3 tw:px-3 tw:py-1 tw:rounded-full tw:bg-gradient-to-r tw:from-pink-100 tw:to-pink-200 tw:text-pink-600 tw:hover:tw:from-pink-200 tw:hover:tw:to-pink-300 tw:transition-all"
@click="openBilibili(item.bilibiliUrl)"
>
B站
</a-button>
</div>
</div>
</div>
<div class="tw:tw:py-6">
<div class="tw:w-full tw:h-px tw:bg-gradient-to-r tw:from-transparent tw:via-gray-300 tw:to-transparent"></div>
</div>
<div class="tw:bg-white tw:rounded-2xl tw:shadow-lg tw:p-8 tw:animate-fade-in-up tw:delay-100">
<h1 class="tw:text-xl tw:text-center tw:font-bold tw:bg-gradient-to-r tw:from-amber-500 tw:to-orange-500 tw:bg-clip-text tw:text-transparent tw:mb-8 tw:flex tw:items-center tw:justify-center tw:gap-3">
<span class="tw:text-2xl">💎</span>
<span>{{ t('about.sponsor') }}</span>
</h1>
<div class="tw:flex tw:flex-wrap tw:justify-center tw:gap-6">
<div
v-for="sponsor in sponsors"
:key="sponsor.id"
class="tw:flex tw:flex-col tw:items-center tw:w-44 tw:p-5 tw:bg-gradient-to-br tw:from-amber-50 tw:to-orange-50 tw:rounded-2xl tw:shadow-md tw:cursor-pointer tw:hover:shadow-2xl tw:hover:-translate-y-2 tw:transition-all duration-300 tw:group tw:border tw:border-amber-100"
@click="contant(sponsor)"
>
<div class="tw:w-24 tw:h-24 tw:flex tw:items-center tw:justify-center tw:bg-gradient-to-br tw:from-white tw:to-amber-100 tw:rounded-2xl tw:p-3 tw:mb-4 tw:group-hover:tw:scale-110 tw:transition-transform duration-300">
<img class="tw:max-w-full tw:max-h-full tw:object-contain" :src="sponsor.imageUrl" :alt="sponsor.name">
</div>
<h2 class="tw:text-base tw:font-bold tw:text-gray-800 tw:group-hover:tw:text-amber-600 tw:transition-colors">{{ sponsor.name }}</h2>
<span class="tw:text-xs tw:text-amber-600 tw:bg-amber-100 tw:px-3 tw:py-1 tw:rounded-full tw:mt-3 tw:font-medium">
{{ sponsor.type }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.tw-animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
.tw-animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
.delay-100 {
animation-delay: 0.1s;
}
</style>

View File

@@ -0,0 +1,175 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue';
import { FileSearchOutlined, FolderOpenOutlined } from '@ant-design/icons-vue';
import { open } from '@tauri-apps/plugin-dialog';
interface ModCheckResult {
filename: string;
filePath: string;
clientSide: 'required' | 'optional' | 'unsupported' | 'unknown';
serverSide: 'required' | 'optional' | 'unsupported' | 'unknown';
source: string;
checked: boolean;
errors?: string[];
allResults: any[];
}
const selectedFolder = ref<string>('');
const bundleName = ref<string>('');
const checking = ref(false);
const results = ref<ModCheckResult[]>([]);
const showResults = ref(false);
async function selectFolder() {
try {
const selected = await open({
directory: true,
multiple: false,
title: '选择 mods 文件夹'
});
if (selected) {
selectedFolder.value = selected;
message.success(`已选择文件夹: ${selected}`);
}
} catch (error) {
console.error('选择文件夹失败:', error);
message.error('选择文件夹失败');
}
}
async function handleCheck() {
if (!selectedFolder.value) {
message.warning('请先选择 mods 文件夹');
return;
}
if (!bundleName.value.trim()) {
message.warning('请输入整合包名字');
return;
}
checking.value = true;
showResults.value = false;
results.value = [];
try {
const response = await fetch('http://localhost:37019/modcheck/folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
folderPath: selectedFolder.value,
bundleName: bundleName.value.trim()
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `请求失败: ${response.status}`);
}
const data = await response.json();
results.value = data;
showResults.value = true;
message.success(`检查完成,共检查 ${data.length} 个模组`);
} catch (error: any) {
console.error('检查失败:', error);
message.error(`检查失败: ${error.message}`);
} finally {
checking.value = false;
}
}
</script>
<template>
<div class="tw:h-full tw:w-full tw:flex tw:flex-col tw:p-4 md:tw:p-6 tw:overflow-auto">
<div class="tw:mb-4 md:tw:mb-6">
<h1 class="tw:text-xl md:tw:text-2xl tw:font-bold tw:mb-2">模组检查</h1>
<p class="tw:text-gray-500 tw:text-sm md:tw:text-base">检查模组是否可以在客户端或服务端使用</p>
</div>
<a-card class="tw:mb-4 md:tw:mb-6">
<div class="tw:mb-4">
<a-button
type="default"
size="large"
block
@click="selectFolder"
class="tw:mb-4"
>
<template #icon>
<FolderOpenOutlined />
</template>
选择 mods 文件夹
</a-button>
<div v-if="selectedFolder" class="tw:mb-4 tw:p-3 tw:bg-gray-50 tw:rounded tw:text-sm tw:text-gray-600">
<span class="tw:font-medium">已选择:</span> {{ selectedFolder }}
</div>
</div>
<div class="tw:mb-4">
<a-input
v-model:value="bundleName"
placeholder="请输入整合包名字"
size="large"
allow-clear
/>
<div class="tw:mt-2 tw:text-xs tw:text-gray-400">
客户端模组将保存到 .rubbish/{{ bundleName || '整合包名字' }} 目录
</div>
</div>
<a-button
type="primary"
size="large"
:loading="checking"
block
@click="handleCheck"
:disabled="checking || !selectedFolder || !bundleName.trim()"
>
<template #icon>
<FileSearchOutlined />
</template>
{{ checking ? '检查中...' : '开始检查' }}
</a-button>
</a-card>
<a-card v-if="showResults" title="检查结果">
<div class="tw:overflow-x-auto">
<a-table
:dataSource="results"
:pagination="false"
:scroll="{ y: 300, x: 'max-content' }"
size="small"
:bordered="true"
>
<a-table-column title="模组信息" key="modInfo" :width="250">
<template #default="{ record }">
<div class="tw:overflow-hidden tw:flex-1 tw:min-w-0">
<div class="tw:font-medium tw:truncate tw:text-sm md:tw:text-base">{{ record.filename }}</div>
</div>
</template>
</a-table-column>
<a-table-column title="类型" key="type" :width="100">
<template #default="{ record }">
<a-tag v-if="record.clientSide === 'required' || record.clientSide === 'optional'" color="purple">
客户端模组
</a-tag>
<a-tag v-else-if="record.serverSide === 'required' || record.serverSide === 'optional'" color="blue">
服务端模组
</a-tag>
<a-tag v-else color="gray">
未知
</a-tag>
</template>
</a-table-column>
</a-table>
</div>
</a-card>
<a-empty v-if="showResults && results.length === 0" description="未找到模组文件" />
</div>
</template>

View File

@@ -0,0 +1,22 @@
<template>
<div class="tw:h-full tw:w-full tw:flex tw:flex-col tw:justify-center tw:items-center">
<div class="tw:w-32 tw:h-32 tw:mb-25">
<svg class="w-32 h-32 mb-4" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="50" fill="#ef4444" />
<path d="M40,40 L80,80 M80,40 L40,80" stroke="white" stroke-width="10" stroke-linecap="round" />
</svg>
<p class="tw:text-2xl tw:font-bold tw:text-center tw:mb-20 tw:text-red-500">Error</p>
<p class="tw:text-sm tw:text-center tw:text-gray-500">
{{ errorMessage }}
</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const errorReason = route.query.e as string;
const errorMessage = errorReason ? `错误原因:${errorReason}` : 'DeEarthX.Core 启动失败!';
</script>

View File

@@ -0,0 +1,270 @@
<template>
<div class="tw:h-full tw:w-full tw:p-4 tw:overflow-auto tw:bg-gray-50">
<div class="tw:max-w-2xl tw:mx-auto">
<div class="tw:text-center tw:mb-8">
<h1 class="tw:text-2xl tw:font-bold tw:tracking-tight">
<span
class="tw:bg-gradient-to-r tw:from-cyan-300 tw:to-purple-950 tw:bg-clip-text tw:text-transparent">
{{ t('galaxy.title') }}
</span>
</h1>
<p class="tw:text-gray-500 tw:mt-2">{{ t('galaxy.subtitle') }}</p>
</div>
<div class="tw:bg-white tw:rounded-lg tw:shadow-sm tw:p-6 tw:mb-6">
<h2 class="tw:text-lg tw:font-semibold tw:text-gray-800 tw:mb-4 tw:flex tw:items-center tw:gap-2">
<span class="tw:w-2 tw:h-2 tw:bg-purple-500 tw:rounded-full"></span>
{{ t('galaxy.mod_submit_title') }}
</h2>
<div class="tw:flex tw:flex-col tw:gap-4">
<div>
<label class="tw:block tw:text-sm tw:font-medium tw:text-gray-700 tw:mb-2">
{{ t('galaxy.mod_type_label') }}
</label>
<a-radio-group v-model:value="modType" size="default" button-style="solid">
<a-radio-button value="client">{{ t('galaxy.mod_type_client') }}</a-radio-button>
<a-radio-button value="server">{{ t('galaxy.mod_type_server') }}</a-radio-button>
</a-radio-group>
</div>
<div>
<label class="tw:block tw:text-sm tw:font-medium tw:text-gray-700 tw:mb-2">
{{ t('galaxy.modid_label') }}
</label>
<a-input
v-model:value="modidInput"
:placeholder="t('galaxy.modid_placeholder')"
size="large"
allow-clear
/>
<p class="tw:text-xs tw:text-gray-400 tw:mt-1">
{{ t('galaxy.modid_count', { count: modidList.length }) }}
</p>
</div>
<div>
<label class="tw:block tw:text-sm tw:font-medium tw:text-gray-700 tw:mb-2">
{{ t('galaxy.upload_file_label') }}
</label>
<a-upload-dragger
:fileList="fileList"
:before-upload="beforeUpload"
@remove="handleRemove"
accept=".jar"
multiple
>
<p class="tw-ant-upload-drag-icon">
<InboxOutlined />
</p>
<p class="tw-ant-upload-text">{{ t('galaxy.upload_file_hint') }}</p>
<p class="tw-ant-upload-hint">
{{ t('galaxy.upload_file_support') }}
</p>
</a-upload-dragger>
<div v-if="fileList.length > 0" class="tw:mt-4">
<p class="tw:text-sm tw:font-medium tw:text-gray-700 tw:mb-2">
{{ t('galaxy.file_selected', { count: fileList.length }) }}
</p>
<div v-if="uploading" class="tw:mb-4">
<a-progress :percent="uploadProgress" :status="uploadProgress === 100 ? 'success' : 'active'" />
</div>
<a-button
type="primary"
size="large"
:loading="uploading"
block
@click="handleUpload"
>
<template #icon>
<UploadOutlined />
</template>
{{ uploading ? t('galaxy.uploading') : t('galaxy.start_upload') }}
</a-button>
</div>
</div>
<div v-if="modidList.length > 0" class="tw:mt-2">
<a-button
type="primary"
size="large"
:loading="submitting"
block
@click="handleSubmit"
>
<template #icon>
<SendOutlined />
</template>
{{ submitting ? t('galaxy.submitting') : t('galaxy.submit', { type: modType === 'client' ? t('galaxy.mod_type_client') : t('galaxy.mod_type_server') }) }}
</a-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { UploadOutlined, InboxOutlined, SendOutlined } from '@ant-design/icons-vue';
import { message, Modal } from 'ant-design-vue';
import type { UploadFile, UploadProps } from 'ant-design-vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const modType = ref<'client' | 'server'>('client');
const modidList = ref<string[]>([]);
const uploading = ref(false);
const submitting = ref(false);
const fileList = ref<UploadFile[]>([]);
const uploadProgress = ref(0);
const modidInput = computed({
get: () => modidList.value.join(','),
set: (value: string) => {
modidList.value = value
.split(',')
.map(id => id.trim())
.filter(id => id.length > 0);
}
});
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
console.log(file.name);
const uploadFile: UploadFile = {
uid: `${Date.now()}-${Math.random()}`,
name: file.name,
status: 'done',
url: '',
originFileObj: file,
};
fileList.value = [...fileList.value, uploadFile];
return false;
};
const handleRemove: UploadProps['onRemove'] = (file) => {
const index = fileList.value.indexOf(file);
const newFileList = fileList.value.slice();
newFileList.splice(index, 1);
fileList.value = newFileList;
};
const handleUpload = async () => {
if (fileList.value.length === 0) {
message.warning(t('galaxy.please_select_file'));
return;
}
uploading.value = true;
uploadProgress.value = 0;
const formData = new FormData();
fileList.value.forEach((file) => {
if (file.originFileObj) {
const blob = file.originFileObj;
const encodedFileName = encodeURIComponent(file.name);
const fileWithCorrectName = new File([blob], encodedFileName, { type: blob.type });
formData.append('files', fileWithCorrectName);
}
});
try {
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:37019/galaxy/upload', true);
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
uploadProgress.value = Math.round((event.loaded / event.total) * 100);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
console.log(data);
if (data.modids && Array.isArray(data.modids)) {
let addedCount = 0;
data.modids.forEach((modid: string) => {
if (modid && !modidList.value.includes(modid)) {
modidList.value.push(modid);
addedCount++;
}
});
message.success(t('galaxy.upload_success', { count: addedCount }));
} else {
message.error(t('galaxy.data_format_error'));
}
resolve(xhr.responseText);
} catch (e) {
message.error(t('galaxy.data_format_error'));
reject(e);
}
} else {
message.error(t('galaxy.upload_failed'));
reject(new Error(`HTTP ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
message.error(t('galaxy.upload_error'));
reject(new Error('网络错误'));
});
xhr.addEventListener('abort', () => {
message.error(t('galaxy.upload_error'));
reject(new Error('上传已取消'));
});
xhr.send(formData);
});
} catch (error) {
console.error('上传失败:', error);
} finally {
uploading.value = false;
fileList.value = [];
uploadProgress.value = 0;
}
};
const handleSubmit = () => {
const modTypeText = modType.value === 'client' ? t('galaxy.mod_type_client') : t('galaxy.mod_type_server');
Modal.confirm({
title: t('galaxy.submit_confirm_title'),
content: t('galaxy.submit_confirm_content', { count: modidList.value.length, type: modTypeText }),
okText: t('common.confirm'),
cancelText: t('common.cancel'),
onOk: async () => {
submitting.value = true;
try {
const apiUrl = modType.value === 'client'
? 'http://localhost:37019/galaxy/submit/client'
: 'http://localhost:37019/galaxy/submit/server';
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
modids: modidList.value,
}),
});
if (response.ok) {
message.success(t('galaxy.submit_success', { type: modTypeText }));
modidList.value = [];
} else {
message.error(t('galaxy.submit_failed'));
}
} catch (error) {
message.error(t('galaxy.submit_error'));
} finally {
submitting.value = false;
}
},
});
};
</script>

812
front/src/views/Main.vue Normal file
View File

@@ -0,0 +1,812 @@
<script lang="ts" setup>
import { inject, ref, onMounted, computed } from 'vue';
import { InboxOutlined } from '@ant-design/icons-vue';
import { message, notification, StepsProps } from 'ant-design-vue';
import type { UploadFile, UploadChangeParam } from 'ant-design-vue';
import { sendNotification } from '@tauri-apps/plugin-notification';
import { SelectProps } from 'ant-design-vue/es/vc-select';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface Template {
id: string;
metadata: {
name: string;
version: string;
description: string;
author: string;
created: string;
type: string;
};
}
// 进度步骤配置
const showSteps = ref(false);
const currentStep = ref(0);
// 模板选择相关
const showTemplateModal = ref(false);
const templates = ref<Template[]>([]);
const loadingTemplates = ref(false);
const selectedTemplate = ref<string>('0');
// 步骤项使用computed自动响应语言变化
const stepItems = computed<Required<StepsProps>['items']>(() => {
return [
{ title: t('home.step1_title'), description: t('home.step1_desc') },
{ title: t('home.step2_title'), description: t('home.step2_desc') },
{ title: t('home.step3_title'), description: t('home.step3_desc') },
{ title: t('home.step4_title'), description: t('home.step4_desc') }
];
});
// 文件上传相关
const uploadedFiles = ref<UploadFile[]>([]);
const uploadDisabled = ref(false);
const startButtonDisabled = ref(false);
// 阻止默认上传行为
function beforeUpload() {
return false;
}
// 处理文件上传变更
function handleFileChange(info: UploadChangeParam) {
if (info.file.status === 'removed') {
uploadDisabled.value = false;
return;
}
if (info.file.status === 'uploading') {
message.loading(t('home.preparing_file'));
return;
}
if (info.file.status === 'done') {
message.success(t('home.file_prepared'));
}
if (!info.file.name?.endsWith('.zip') && !info.file.name?.endsWith('.mrpack')) {
message.error(t('home.only_zip_mrpack'));
return;
}
uploadDisabled.value = true;
}
// 处理文件拖拽(预留功能)
function handleFileDrop(e: DragEvent) {
console.log(e);
}
// 初始化
onMounted(() => {
// stepItems 和 modeOptions 都是 computed会自动初始化
});
// 重置所有状态
function resetState() {
uploadedFiles.value = [];
uploadDisabled.value = false;
startButtonDisabled.value = false;
showSteps.value = false;
currentStep.value = 0;
unzipProgress.value = { status: 'active', percent: 0, display: true };
downloadProgress.value = { status: 'active', percent: 0, display: true };
const killCoreProcess = inject("killCoreProcess");
if (killCoreProcess && typeof killCoreProcess === 'function') {
killCoreProcess();
}
}
// 模式选择相关
const javaAvailable = ref(true);
const selectedMode = ref(javaAvailable.value ? 'server' : 'upload');
// 模式选项使用computed自动响应语言变化
const modeOptions = computed<SelectProps['options']>(() => {
return [
{ label: t('home.mode_server'), value: 'server', disabled: !javaAvailable.value },
{ label: t('home.mode_upload'), value: 'upload', disabled: false }
];
});
// 处理模式选择
function handleModeSelect(value: string) {
selectedMode.value = value;
}
// 加载模板列表
async function loadTemplates() {
loadingTemplates.value = true;
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates`);
const result = await response.json();
if (result.status === 200) {
templates.value = result.data || [];
} else {
message.error(t('home.template_load_failed'));
}
} catch (error) {
console.error('加载模板列表失败:', error);
message.error(t('home.template_load_failed'));
} finally {
loadingTemplates.value = false;
}
}
// 打开模板选择弹窗
function openTemplateModal() {
loadTemplates();
showTemplateModal.value = true;
}
// 选择模板
function selectTemplate(templateId: string) {
selectedTemplate.value = templateId;
showTemplateModal.value = false;
if (templateId === '0') {
message.success(t('home.template_selected') + ': ' + t('home.template_official_loader'));
} else {
const template = templates.value.find(t => t.id === templateId);
if (template) {
message.success(t('home.template_selected') + ': ' + template.metadata.name);
}
}
}
// 获取当前选择的模板名称
const currentTemplateName = computed(() => {
if (selectedTemplate.value === '0' || !selectedTemplate.value) {
return t('home.template_official_loader');
}
const template = templates.value.find(t => t.id === selectedTemplate.value);
return template ? template.metadata.name : t('home.template_official_loader');
});
// 进度显示相关
interface ProgressStatus {
status: 'active' | 'success' | 'exception' | 'normal';
percent: number;
display: boolean;
uploadedSize?: number;
totalSize?: number;
speed?: number;
remainingTime?: number;
}
const unzipProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const downloadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const uploadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const serverInstallProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const filterModsProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: false });
const startTime = ref<number>(0);
const serverInstallInfo = ref({
modpackName: '',
minecraftVersion: '',
loaderType: '',
loaderVersion: '',
currentStep: '',
stepIndex: 0,
totalSteps: 0,
message: '',
status: 'idle' as 'idle' | 'installing' | 'completed' | 'error',
error: '',
installPath: '',
duration: 0
});
const filterModsInfo = ref({
totalMods: 0,
currentMod: 0,
modName: '',
filteredCount: 0,
movedCount: 0,
status: 'idle' as 'idle' | 'filtering' | 'completed' | 'error',
error: '',
duration: 0
});
// 格式化文件大小
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
// 格式化时间
function formatTime(seconds: number): string {
if (seconds < 60) return `${Math.round(seconds)}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${Math.round(seconds % 60)}s`;
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
}
// 运行DeEarthX核心功能
async function runDeEarthX(file: File, ws: WebSocket) {
message.success(t('home.start_production'));
showSteps.value = true;
const formData = new FormData();
formData.append('file', file);
try {
message.loading(t('home.task_preparing'));
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
let url = `http://${apiHost}:${apiPort}/start?mode=${selectedMode.value}`;
if (selectedMode.value === 'server' && selectedTemplate.value) {
url += `&template=${encodeURIComponent(selectedTemplate.value)}`;
}
uploadProgress.value = { status: 'active', percent: 0, display: true };
startTime.value = Date.now();
await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
uploadProgress.value.percent = percent;
uploadProgress.value.uploadedSize = event.loaded;
uploadProgress.value.totalSize = event.total;
// 计算上传速度
const elapsedTime = (Date.now() - startTime.value) / 1000;
if (elapsedTime > 0) {
uploadProgress.value.speed = event.loaded / elapsedTime;
// 计算剩余时间
const remainingBytes = event.total - event.loaded;
uploadProgress.value.remainingTime = remainingBytes / uploadProgress.value.speed;
}
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
uploadProgress.value.status = 'success';
uploadProgress.value.percent = 100;
setTimeout(() => {
uploadProgress.value.display = false;
}, 2000);
resolve(xhr.response);
} else {
uploadProgress.value.status = 'exception';
reject(new Error(`HTTP ${xhr.status}`));
}
});
xhr.addEventListener('error', () => {
uploadProgress.value.status = 'exception';
reject(new Error('网络错误'));
});
xhr.addEventListener('abort', () => {
uploadProgress.value.status = 'exception';
reject(new Error('上传已取消'));
});
xhr.send(formData);
});
} catch (error) {
console.error('请求失败:', error);
message.error(t('home.request_failed'));
uploadProgress.value.status = 'exception';
resetState();
ws.close();
}
}
// 设置WebSocket连接
// 处理错误消息
function handleError(result: any) {
if (result === 'jini') {
javaAvailable.value = false;
notification.error({
message: t('home.java_error_title'),
description: t('home.java_error_desc'),
duration: 0
});
} else if (typeof result === 'string') {
// 根据错误类型提供不同的解决方案
let errorTitle = t('home.backend_error');
let errorDesc = t('home.backend_error_desc', { error: result });
let suggestions: string[] = [];
// 网络相关错误
if (result.includes('network') || result.includes('connection') || result.includes('timeout')) {
errorTitle = t('home.network_error_title');
errorDesc = t('home.network_error_desc', { error: result });
suggestions = [
t('home.suggestion_check_network'),
t('home.suggestion_check_firewall'),
t('home.suggestion_retry')
];
}
// 文件相关错误
else if (result.includes('file') || result.includes('permission') || result.includes('disk')) {
errorTitle = t('home.file_error_title');
errorDesc = t('home.file_error_desc', { error: result });
suggestions = [
t('home.suggestion_check_disk_space'),
t('home.suggestion_check_permission'),
t('home.suggestion_check_file_format')
];
}
// 内存相关错误
else if (result.includes('memory') || result.includes('out of memory') || result.includes('heap')) {
errorTitle = t('home.memory_error_title');
errorDesc = t('home.memory_error_desc', { error: result });
suggestions = [
t('home.suggestion_increase_memory'),
t('home.suggestion_close_other_apps'),
t('home.suggestion_restart_application')
];
}
// 通用错误
else {
suggestions = [
t('home.suggestion_check_backend'),
t('home.suggestion_check_logs'),
t('home.suggestion_contact_support')
];
}
// 构建完整的错误描述
const fullDescription = `${errorDesc}\n\n${t('home.suggestions')}:\n${suggestions.map((s, i) => `${i + 1}. ${s}`).join('\n')}`;
notification.error({
message: errorTitle,
description: fullDescription,
duration: 0
});
resetState();
} else {
notification.error({
message: t('home.unknown_error_title'),
description: t('home.unknown_error_desc'),
duration: 0
});
resetState();
}
}
// 更新解压进度
function updateUnzipProgress(result: { current: number; total: number }) {
unzipProgress.value.percent = Math.round((result.current / result.total) * 100);
if (result.current === result.total) {
unzipProgress.value.status = 'success';
setTimeout(() => {
unzipProgress.value.display = false;
}, 2000);
}
}
// 更新下载进度
function updateDownloadProgress(result: { index: number; total: number }) {
downloadProgress.value.percent = Math.round((result.index / result.total) * 100);
if (downloadProgress.value.percent === 100) {
downloadProgress.value.status = 'success';
setTimeout(() => {
downloadProgress.value.display = false;
}, 2000);
}
}
// 处理完成状态
function handleFinish(result: number) {
const timeSpent = Math.round(result / 1000);
currentStep.value++;
message.success(t('home.production_complete', { time: timeSpent }));
sendNotification({ title: t('common.app_name'), body: t('home.production_complete', { time: timeSpent }) });
// 8秒后自动重置状态
setTimeout(resetState, 8000);
}
// 处理服务端安装开始
function handleServerInstallStart(result: any) {
serverInstallInfo.value = {
modpackName: result.modpackName,
minecraftVersion: result.minecraftVersion,
loaderType: result.loaderType,
loaderVersion: result.loaderVersion,
currentStep: '',
stepIndex: 0,
totalSteps: 0,
message: 'Starting installation...',
status: 'installing',
error: '',
installPath: '',
duration: 0
};
serverInstallProgress.value = { status: 'active', percent: 0, display: true };
}
// 处理服务端安装步骤
function handleServerInstallStep(result: any) {
serverInstallInfo.value.currentStep = result.step;
serverInstallInfo.value.stepIndex = result.stepIndex;
serverInstallInfo.value.totalSteps = result.totalSteps;
serverInstallInfo.value.message = result.message || result.step;
// 计算总体进度
const overallProgress = (result.stepIndex / result.totalSteps) * 100;
serverInstallProgress.value.percent = Math.round(overallProgress);
}
// 处理服务端安装进度
function handleServerInstallProgress(result: any) {
serverInstallInfo.value.currentStep = result.step;
serverInstallInfo.value.message = result.message || result.step;
serverInstallProgress.value.percent = result.progress;
}
// 处理服务端安装完成
function handleServerInstallComplete(result: any) {
serverInstallInfo.value.status = 'completed';
serverInstallInfo.value.installPath = result.installPath;
serverInstallInfo.value.duration = result.duration;
serverInstallInfo.value.message = t('home.server_install_completed');
serverInstallProgress.value = { status: 'success', percent: 100, display: true };
// 跳转到完成步骤
currentStep.value++;
const timeSpent = Math.round(result.duration / 1000);
message.success(t('home.server_install_completed') + ` ${t('home.server_install_duration')}: ${timeSpent}s`);
sendNotification({ title: t('common.app_name'), body: t('home.production_complete', { time: timeSpent }) });
// 8秒后隐藏进度
setTimeout(() => {
serverInstallProgress.value.display = false;
}, 8000);
}
// 处理服务端安装错误
function handleServerInstallError(result: any) {
serverInstallInfo.value.status = 'error';
serverInstallInfo.value.error = result.error;
serverInstallInfo.value.message = result.error;
serverInstallProgress.value = { status: 'exception', percent: serverInstallProgress.value.percent, display: true };
notification.error({
message: t('home.server_install_error'),
description: result.error,
duration: 0
});
}
// 处理筛选模组开始
function handleFilterModsStart(result: any) {
filterModsInfo.value = {
totalMods: result.totalMods,
currentMod: 0,
modName: '',
filteredCount: 0,
movedCount: 0,
status: 'filtering',
error: '',
duration: 0
};
filterModsProgress.value = { status: 'active', percent: 0, display: true };
}
// 处理筛选模组进度
function handleFilterModsProgress(result: any) {
filterModsInfo.value.currentMod = result.current;
filterModsInfo.value.modName = result.modName;
const percent = Math.round((result.current / result.total) * 100);
filterModsProgress.value.percent = percent;
}
// 处理筛选模组完成
function handleFilterModsComplete(result: any) {
filterModsInfo.value.status = 'completed';
filterModsInfo.value.filteredCount = result.filteredCount;
filterModsInfo.value.movedCount = result.movedCount;
filterModsInfo.value.duration = result.duration;
filterModsProgress.value = { status: 'success', percent: 100, display: true };
const timeSpent = Math.round(result.duration / 1000);
message.success(t('home.filter_mods_completed', { filtered: result.filteredCount, moved: result.movedCount }) + ` ${t('home.server_install_duration')}: ${timeSpent}s`);
// 8秒后隐藏进度
setTimeout(() => {
filterModsProgress.value.display = false;
}, 8000);
}
// 处理筛选模组错误
function handleFilterModsError(result: any) {
filterModsInfo.value.status = 'error';
filterModsInfo.value.error = result.error;
filterModsProgress.value = { status: 'exception', percent: filterModsProgress.value.percent, display: true };
notification.error({
message: t('home.filter_mods_error'),
description: result.error,
duration: 0
});
}
// 开始处理文件
function handleStartProcess() {
if (uploadedFiles.value.length === 0) {
message.warning(t('home.please_select_file'));
return;
}
const file = uploadedFiles.value[0].originFileObj;
if (!file) return;
startButtonDisabled.value = true;
uploadDisabled.value = true;
showSteps.value = true;
message.loading(t('home.ws_connecting'));
const wsHost = import.meta.env.VITE_WS_HOST || 'localhost';
const wsPort = import.meta.env.VITE_WS_PORT || '37019';
const ws = new WebSocket(`ws://${wsHost}:${wsPort}/`);
ws.addEventListener('open', () => {
message.success(t('home.ws_connected'));
runDeEarthX(file, ws);
});
ws.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'error') {
handleError(data.message);
} else if (data.type === 'info') {
message.info(data.message);
} else if (data.status) {
switch (data.status) {
case 'error':
handleError(data.result);
break;
case 'changed':
currentStep.value++;
break;
case 'unzip':
updateUnzipProgress(data.result);
break;
case 'downloading':
updateDownloadProgress(data.result);
break;
case 'finish':
handleFinish(data.result);
break;
case 'server_install_start':
handleServerInstallStart(data.result);
break;
case 'server_install_step':
handleServerInstallStep(data.result);
break;
case 'server_install_progress':
handleServerInstallProgress(data.result);
break;
case 'server_install_complete':
handleServerInstallComplete(data.result);
break;
case 'server_install_error':
handleServerInstallError(data.result);
break;
case 'filter_mods_start':
handleFilterModsStart(data.result);
break;
case 'filter_mods_progress':
handleFilterModsProgress(data.result);
break;
case 'filter_mods_complete':
handleFilterModsComplete(data.result);
break;
case 'filter_mods_error':
handleFilterModsError(data.result);
break;
}
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
notification.error({ message: t('common.error'), description: t('home.parse_error') });
}
});
ws.addEventListener('error', () => {
notification.error({
message: t('home.ws_error_title'),
description: `${t('home.ws_error_desc')}\n\n${t('home.suggestions')}:\n1. ${t('home.suggestion_check_backend')}\n2. ${t('home.suggestion_check_port')}\n3. ${t('home.suggestion_restart_application')}`,
duration: 0
});
resetState();
});
ws.addEventListener('close', () => {
console.log('WebSocket连接关闭');
});
}
</script>
<template>
<div class="tw:h-full tw:w-full tw:relative tw:flex tw:flex-col">
<div class="tw:flex-1 tw:w-full tw:flex tw:flex-col tw:justify-center tw:items-center tw:p-4">
<div class="tw:w-full tw:max-w-2xl tw:flex tw:flex-col tw:items-center">
<div>
<h1 class="tw:text-4xl tw:text-center tw:animate-pulse">{{ t('common.app_name') }}</h1>
<h1 class="tw:text-sm tw:text-gray-500 tw:text-center">{{ t('home.title') }}</h1>
</div>
<a-upload-dragger :disabled="uploadDisabled" class="tw:w-full tw:max-w-md tw:h-48" name="file"
action="/" :multiple="false" :before-upload="beforeUpload" @change="handleFileChange"
@drop="handleFileDrop" v-model:fileList="uploadedFiles" accept=".zip,.mrpack">
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text">{{ t('home.upload_title') }}</p>
<p class="ant-upload-hint">
{{ t('home.upload_hint') }}
</p>
</a-upload-dragger>
<div class="tw:flex tw:items-center tw:gap-2 tw:mt-8">
<a-select ref="select" :options="modeOptions" :value="selectedMode"
style="width: 120px;" @select="handleModeSelect"></a-select>
<a-button v-if="selectedMode === 'server'" @click="openTemplateModal">
{{ t('home.template_select_button') }}
</a-button>
</div>
<div v-if="selectedMode === 'server'" class="tw:text-xs tw:text-gray-500 tw:mt-2">
{{ t('home.template_selected') }}: {{ currentTemplateName }}
</div>
<a-button :disabled="startButtonDisabled" type="primary" @click="handleStartProcess"
style="margin-top: 6px">
{{ t('common.start') }}
</a-button>
</div>
</div>
<div v-if="showSteps"
class="tw:fixed tw:bottom-2 tw:left-1/2 tw:-translate-x-1/2 tw:w-[65%] tw:h-20 tw:flex tw:justify-center tw:items-center tw:text-sm tw:bg-white tw:rounded-xl tw:shadow-lg tw:px-4 tw:ml-10">
<a-steps :current="currentStep" :items="stepItems" size="small" />
</div>
<div v-if="showSteps" ref="logContainer"
class="tw:absolute tw:right-2 tw:bottom-32 tw:h-80 tw:w-64 tw:rounded-xl tw:overflow-y-auto">
<a-card :title="t('home.progress_title')" :bordered="true" class="tw:h-full">
<div v-if="uploadProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.upload_progress') }}</h1>
<a-progress :percent="uploadProgress.percent" :status="uploadProgress.status" size="small" />
<div v-if="uploadProgress.totalSize" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ formatFileSize(uploadProgress.uploadedSize || 0) }} / {{ formatFileSize(uploadProgress.totalSize) }}
<span v-if="uploadProgress.speed" class="tw:ml-2">
{{ t('home.speed') }}: {{ formatFileSize(uploadProgress.speed) }}/s
</span>
<span v-if="uploadProgress.remainingTime" class="tw:ml-2">
{{ t('home.remaining') }}: {{ formatTime(uploadProgress.remainingTime) }}
</span>
</div>
</div>
<div v-if="unzipProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.unzip_progress') }}</h1>
<a-progress :percent="unzipProgress.percent" :status="unzipProgress.status" size="small" />
</div>
<div v-if="downloadProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.download_progress') }}</h1>
<a-progress :percent="downloadProgress.percent" :status="downloadProgress.status" size="small" />
</div>
<div v-if="serverInstallProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.server_install_progress') }}</h1>
<a-progress :percent="serverInstallProgress.percent" :status="serverInstallProgress.status" size="small" />
<div v-if="serverInstallInfo.currentStep" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ t('home.server_install_step') }}: {{ serverInstallInfo.currentStep }}
<span v-if="serverInstallInfo.totalSteps > 0">
({{ serverInstallInfo.stepIndex }}/{{ serverInstallInfo.totalSteps }})
</span>
</div>
<div v-if="serverInstallInfo.message" class="tw:text-xs tw:text-gray-600 tw:mt-1 tw:break-words">
{{ t('home.server_install_message') }}: {{ serverInstallInfo.message }}
</div>
<div v-if="serverInstallInfo.status === 'completed'" class="tw:text-xs tw:text-green-600 tw:mt-1">
{{ t('home.server_install_completed') }} {{ t('home.server_install_duration') }}: {{ (serverInstallInfo.duration / 1000).toFixed(2) }}s
</div>
<div v-if="serverInstallInfo.status === 'error'" class="tw:text-xs tw:text-red-600 tw:mt-1 tw:break-words">
{{ t('home.server_install_error') }}: {{ serverInstallInfo.error }}
</div>
</div>
<div v-if="filterModsProgress.display" class="tw:mb-4">
<h1 class="tw:text-sm">{{ t('home.filter_mods_progress') }}</h1>
<a-progress :percent="filterModsProgress.percent" :status="filterModsProgress.status" size="small" />
<div v-if="filterModsInfo.totalMods > 0" class="tw:text-xs tw:text-gray-500 tw:mt-1">
{{ t('home.filter_mods_total') }}: {{ filterModsInfo.totalMods }}
</div>
<div v-if="filterModsInfo.modName" class="tw:text-xs tw:text-gray-600 tw:mt-1 tw:break-words">
{{ t('home.filter_mods_current') }}: {{ filterModsInfo.modName }}
</div>
<div v-if="filterModsInfo.status === 'completed'" class="tw:text-xs tw:text-green-600 tw:mt-1">
{{ t('home.filter_mods_completed', { filtered: filterModsInfo.filteredCount, moved: filterModsInfo.movedCount }) }}
</div>
<div v-if="filterModsInfo.status === 'error'" class="tw:text-xs tw:text-red-600 tw:mt-1 tw:break-words">
{{ t('home.filter_mods_error') }}: {{ filterModsInfo.error }}
</div>
</div>
</a-card>
</div>
<a-modal v-model:open="showTemplateModal" :title="t('home.template_select_title')" :footer="null" width="700px">
<a-spin :spinning="loadingTemplates">
<div class="tw:mb-4">
<p class="tw:mb-2 tw:text-gray-600">{{ t('home.template_select_desc') }}</p>
<!-- 导入模板 -->
<!-- <a-upload-dragger name="file" action="/" :multiple="false" :before-upload="beforeUpload" @change="handleImportTemplateChange" accept=".zip">
<p class="ant-upload-drag-icon">
<inbox-outlined></inbox-outlined>
</p>
<p class="ant-upload-text">{{ t('home.template_import_title') }}</p>
<p class="ant-upload-hint">
{{ t('home.template_import_hint') }}
</p>
</a-upload-dragger> -->
</div>
<div class="tw:max-h-96 tw:overflow-y-auto tw:pr-2">
<div class="tw:grid tw:grid-cols-2 tw:gap-3">
<div
@click="selectTemplate('0')"
:class="[
'tw:p-3 tw:rounded-lg tw:cursor-pointer tw:border-2 tw:transition-all tw:tw:h-32 tw:flex tw:flex-col tw:justify-between',
selectedTemplate === '0' ? 'tw:border-blue-500 tw:bg-blue-50' : 'tw:border-gray-200 hover:tw:border-gray-300'
]"
>
<div>
<h3 class="tw:text-base tw:font-semibold tw:mb-1">{{ t('home.template_official_loader') }}</h3>
<p class="tw:text-xs tw:text-gray-600 tw:line-clamp-2">{{ t('home.template_official_loader_desc') }}</p>
</div>
</div>
<div
v-for="template in templates"
:key="template.id"
@click="selectTemplate(template.id)"
:class="[
'tw:p-3 tw:rounded-lg tw:cursor-pointer tw:border-2 tw:transition-all tw:h-32 tw:flex tw:flex-col tw:justify-between',
selectedTemplate === template.id ? 'tw:border-blue-500 tw:bg-blue-50' : 'tw:border-gray-200 hover:tw:border-gray-300'
]"
>
<div class="tw:flex-1 tw:overflow-hidden">
<div class="tw:flex tw:justify-between tw:items-start tw:mb-1">
<h3 class="tw:text-base tw:font-semibold tw:truncate tw:flex-1">{{ template.metadata.name }}</h3>
<!-- <a-button size="small" type="link" @click.stop="exportTemplate(template.id)">
{{ t('home.template_export_button') }}
</a-button> -->
</div>
<p class="tw:text-xs tw:text-gray-600 tw:line-clamp-2 tw:mb-2">{{ template.metadata.description }}</p>
</div>
<div class="tw:flex tw:justify-between tw:text-xs tw:text-gray-500 tw:mt-1">
<span class="tw:truncate tw:max-w-[50%]">{{ template.metadata.author }}</span>
<a-tag color="blue" size="small" class="tw:text-xs tw:px-1 tw:py-0.5 tw:truncate tw:max-w-[45%]">{{ template.metadata.version }}</a-tag>
</div>
</div>
</div>
<div v-if="templates.length === 0 && !loadingTemplates" class="tw:text-center tw:py-8 tw:text-gray-500">
{{ t('template.empty') }}
</div>
</div>
</a-spin>
</a-modal>
</div>
</template>

View File

@@ -0,0 +1,324 @@
<script lang="ts" setup>
import { ref, watch, onMounted, computed } from 'vue';
import { message } from 'ant-design-vue';
import { useI18n } from 'vue-i18n';
import { setLanguage, type Language } from '../utils/i18n';
import axios from '../utils/axios';
interface AppConfig {
mirror: {
bmclapi: boolean;
mcimirror: boolean;
};
filter: {
hashes: boolean;
dexpub: boolean;
mixins: boolean;
modrinth: boolean;
};
oaf: boolean;
autoZip: boolean;
javaPath?: string;
}
interface SettingItem {
key: string;
name: string;
description: string;
path: string;
defaultValue: boolean;
}
interface SettingCategory {
id: string;
title: string;
icon: string;
bgColor: string;
textColor: string;
items: SettingItem[];
hasJava?: boolean;
hasLanguage?: boolean;
}
const config = ref<AppConfig>({
mirror: { bmclapi: false, mcimirror: false },
filter: { hashes: false, dexpub: false, mixins: false, modrinth: false },
oaf: false,
autoZip: false,
javaPath: undefined
});
const settings = computed<SettingCategory[]>(() => {
return [
{
id: 'filter',
title: t('setting.category_filter'),
icon: '🧩',
bgColor: 'bg-emerald-100',
textColor: 'text-emerald-800',
items: [
{
key: 'hashes',
name: t('setting.filter_hashes_name'),
description: t('setting.filter_hashes_desc'),
path: 'filter.hashes',
defaultValue: false
},
{
key: 'dexpub',
name: t('setting.filter_dexpub_name'),
description: t('setting.filter_dexpub_desc'),
path: 'filter.dexpub',
defaultValue: false
},
{
key: 'modrinth',
name: t('setting.filter_modrinth_name'),
description: t('setting.filter_modrinth_desc'),
path: 'filter.modrinth',
defaultValue: false
},
{
key: 'mixins',
name: t('setting.filter_mixins_name'),
description: t('setting.filter_mixins_desc'),
path: 'filter.mixins',
defaultValue: false
}
]
},
{
id: 'mirror',
title: t('setting.category_mirror'),
icon: '⬇️',
bgColor: 'bg-cyan-100',
textColor: 'text-cyan-800',
items: [
{
key: 'mcimirror',
name: t('setting.mirror_mcimirror_name'),
description: t('setting.mirror_mcimirror_desc'),
path: 'mirror.mcimirror',
defaultValue: false
},
{
key: 'bmclapi',
name: t('setting.mirror_bmclapi_name'),
description: t('setting.mirror_bmclapi_desc'),
path: 'mirror.bmclapi',
defaultValue: false
}
]
},
{
id: 'system',
title: t('setting.category_system'),
icon: '🛠️',
bgColor: 'bg-purple-100',
textColor: 'text-purple-800',
hasLanguage: true,
items: [
{
key: 'oaf',
name: t('setting.system_oaf_name'),
description: t('setting.system_oaf_desc'),
path: 'oaf',
defaultValue: false
},
{
key: 'autoZip',
name: t('setting.system_autozip_name'),
description: t('setting.system_autozip_desc'),
path: 'autoZip',
defaultValue: false
}
]
}
];
});
const languageOptions = computed(() => {
return [
{ label: '简体中文', value: 'zh_cn' },
{ label: '繁體中文(香港)', value: 'zh_hk' },
{ label: '繁體中文(台灣)', value: 'zh_tw' },
{ label: 'English', value: 'en_us' },
{ label: '日本語', value: 'ja_jp' },
{ label: 'Français', value: 'fr_fr' },
{ label: 'Deutsch', value: 'de_de' },
{ label: 'Español', value: 'es_es' }
];
});
function handleLanguageChange(value: Language) {
setLanguage(value);
}
const { t, locale } = useI18n();
function getConfigValue(path: string): boolean {
const keys = path.split('.');
let value: any = config.value;
for (const key of keys) {
value = value[key];
}
if (typeof value === 'boolean') {
return value;
}
console.warn(`Config value at path "${path}" is not a boolean:`, value);
return false;
}
function setConfigValue(path: string, newValue: boolean): void {
const keys = path.split('.');
let obj: any = config.value;
for (let i = 0; i < keys.length - 1; i++) {
obj = obj[keys[i]];
}
obj[keys[keys.length - 1]] = newValue;
}
async function loadConfig() {
try {
const response = await axios.get('/config/get');
config.value = response.data;
console.log('[Setting] 配置已从后端刷新');
} catch (error) {
console.error('加载配置失败:', error);
message.error(t('setting.config_load_failed'));
}
}
defineExpose({
refreshConfig: loadConfig
});
async function saveConfig(newConfig: AppConfig) {
try {
await axios.post('/config/post', newConfig, {
headers: { 'Content-Type': 'application/json' }
});
message.success(t('setting.config_saved'));
} catch (error) {
console.error('保存配置失败:', error);
message.error(t('setting.config_save_failed'));
}
}
onMounted(() => {
loadConfig();
});
let isInitialLoad = true;
watch(config, (newValue) => {
if (isInitialLoad) {
isInitialLoad = false;
return;
}
saveConfig(newValue);
}, { deep: true });
</script>
<template>
<div class="tw:h-full tw:w-full tw:p-8 tw:overflow-auto tw:bg-gradient-to-br tw:from-slate-50 tw:via-blue-50 tw:to-indigo-50">
<div class="tw:max-w-3xl tw:mx-auto">
<div class="tw:text-center tw:mb-10 tw:animate-fade-in">
<h1 class="tw:text-4xl tw:font-bold tw:tracking-tight tw:mb-3">
<span class="tw:bg-gradient-to-r tw:from-emerald-500 tw:to-cyan-500 tw:bg-clip-text tw:text-transparent">
{{ t('common.app_name') }}
</span>
<span class="tw:text-gray-800">{{ t('menu.setting') }}</span>
</h1>
<p class="tw:text-gray-500 tw:text-lg">{{ t('setting.subtitle') }}</p>
</div>
<div
v-for="(category, index) in settings"
:key="category.id"
class="tw-bg-white tw:rounded-2xl tw:shadow-lg tw:p-7 tw:mb-6 tw:animate-fade-in-up tw:group tw:border tw:border-gray-100 tw:hover:tw:border-emerald-200 tw:transition-all duration-300"
:style="{ animationDelay: `${index * 0.1}s` }"
>
<h2 class="tw:text-xl tw:font-bold tw:text-gray-800 tw-mb-6 tw:flex tw:items-center tw:group-hover:tw:translate-x-2 tw:transition-transform duration-300">
<span :class="[category.bgColor, category.textColor, 'tw-w-10 tw:h-10 tw:rounded-xl tw:flex tw:items-center tw:justify-center tw-mr-3 tw:shadow-md']">
{{ category.icon }}
</span>
{{ category.title }}
</h2>
<div class="tw:grid tw:grid-cols-1 md:tw:grid-cols-2 lg:tw:grid-cols-3 tw:gap-4">
<div
v-if="category.hasLanguage"
class="tw:flex tw:flex-col tw-justify-between tw-p-4 tw:border tw:border-gray-100 tw:rounded-xl tw-hover:bg-gradient-to-r tw:hover:from-emerald-50 tw:hover:to-cyan-50 tw:hover:border-emerald-200 tw:transition-all duration-300 tw:group/item"
>
<div class="tw:flex-1">
<p class="tw:text-gray-700 tw:font-semibold tw:text-sm tw:group-hover/item:tw:text-emerald-700 tw:transition-colors tw:flex tw:items-center tw:gap-2">
<span>🌐</span> {{ t('setting.language_title') }}
</p>
<p class="tw:text-xs tw:text-gray-500 tw:mt-2">{{ t('setting.language_desc') }}</p>
<div class="tw-mt-3">
<a-select
:value="locale"
:options="languageOptions"
@change="handleLanguageChange"
class="tw:w-full"
/>
</div>
</div>
</div>
<div
v-for="item in category.items"
:key="item.key"
class="tw:flex tw:items-center tw:justify-between tw-p-4 tw:border tw:border-gray-100 tw:rounded-xl tw-hover:bg-gradient-to-r tw:hover:from-emerald-50 tw:hover:to-cyan-50 tw:hover:border-emerald-200 tw:transition-all duration-300 tw:group/item"
>
<div class="tw:flex-1">
<p class="tw:text-gray-700 tw:font-semibold tw:text-sm tw:group-hover/item:tw:text-emerald-700 tw:transition-colors">{{ item.name }}</p>
<p class="tw:text-xs tw:text-gray-500 tw:mt-1">{{ item.description }}</p>
</div>
<a-switch
:checked="getConfigValue(item.path)"
@change="setConfigValue(item.path, $event)"
:checked-children="t('setting.switch_on')"
:un-checked-children="t('setting.switch_off')"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.tw-animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
.tw-animate-fade-in-up {
animation: fadeInUp 0.6s ease-out;
animation-fill-mode: both;
}
</style>

View File

@@ -0,0 +1,823 @@
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { PlusOutlined, DeleteOutlined, FolderOutlined, ExclamationCircleOutlined, EditOutlined, UploadOutlined, DownloadOutlined } from '@ant-design/icons-vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
interface Template {
id: string;
metadata: {
name: string;
version: string;
description: string;
author: string;
created: string;
type: string;
};
}
// 模板商店模板接口
interface StoreTemplate {
id: string;
name: string;
description: string;
size: string;
downloadUrls: string[];
}
const templates = ref<Template[]>([]);
const loading = ref(false);
const showCreateModal = ref(false);
const showDeleteModal = ref(false);
const showEditModal = ref(false);
const deletingTemplate = ref<Template | null>(null);
const editingTemplate = ref<Template | null>(null);
// 导出进度相关状态
const exportLoading = ref(false);
const exportProgress = ref(0);
const showExportProgress = ref(false);
// 导入进度相关状态
const importLoading = ref(false);
const importProgress = ref(0);
const showImportProgress = ref(false);
// 下载进度相关状态
const downloadLoading = ref(false);
const downloadProgress = ref(0);
const showDownloadProgress = ref(false);
// 下载状态管理
const downloadStates = ref<Map<string, { url: string, downloadedSize: number, totalSize: number }>>(new Map());
// 测试下载链接速度
async function testDownloadSpeed(urls: string[]): Promise<string> {
const speedTests = urls.map(async (url) => {
try {
const startTime = performance.now();
const response = await fetch(url, {
method: 'HEAD'
});
if (response.ok) {
const endTime = performance.now();
return { url, time: endTime - startTime };
}
return { url, time: Infinity };
} catch (error) {
console.error(`测试链接 ${url} 失败:`, error);
return { url, time: Infinity };
}
});
const results = await Promise.all(speedTests);
const fastest = results.sort((a, b) => a.time - b.time)[0];
return fastest.url;
}
// 模板商店相关状态
const storeTemplates = ref<StoreTemplate[]>([]);
const storeLoading = ref(false);
const activeTab = ref('local'); // 'local' 或 'store'
const newTemplate = ref({
name: '',
version: '1.0.0',
description: '',
author: ''
});
async function loadTemplates() {
loading.value = true;
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates`);
const result = await response.json();
if (result.status === 200) {
templates.value = result.data || [];
} else {
message.error(t('home.template_load_failed'));
}
} catch (error) {
console.error('加载模板列表失败:', error);
message.error(t('home.template_load_failed'));
} finally {
loading.value = false;
}
}
function openCreateModal() {
newTemplate.value = {
name: '',
version: '1.0.0',
description: '',
author: ''
};
showCreateModal.value = true;
}
async function createTemplate() {
if (!newTemplate.value.name) {
message.error(t('template.name_required'));
return;
}
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newTemplate.value)
});
const result = await response.json();
if (result.status === 200) {
message.success(t('template.create_success'));
showCreateModal.value = false;
await loadTemplates();
} else {
message.error(result.message || t('template.create_failed'));
}
} catch (error) {
console.error('创建模板失败:', error);
message.error(t('template.create_failed'));
}
}
function openDeleteModal(template: Template) {
deletingTemplate.value = template;
showDeleteModal.value = true;
}
async function confirmDelete() {
if (!deletingTemplate.value) return;
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates/${deletingTemplate.value.id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.status === 200) {
message.success(t('template.delete_success'));
showDeleteModal.value = false;
await loadTemplates();
} else {
message.error(result.message || t('template.delete_failed'));
}
} catch (error) {
console.error('删除模板失败:', error);
message.error(t('template.delete_failed'));
}
}
async function openTemplateFolder(template: Template) {
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates/${template.id}/path`);
const result = await response.json();
if (result.status !== 200) {
message.error(result.message || t('template.open_folder_failed'));
}
} catch (error) {
console.error('打开文件夹失败:', error);
message.error(t('template.open_folder_failed'));
}
}
function openEditModal(template: Template) {
editingTemplate.value = template;
newTemplate.value = {
name: template.metadata.name,
version: template.metadata.version,
description: template.metadata.description,
author: template.metadata.author
};
showEditModal.value = true;
}
async function updateTemplate() {
if (!editingTemplate.value || !newTemplate.value.name) {
message.error(t('template.name_required'));
return;
}
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates/${editingTemplate.value.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newTemplate.value)
});
const result = await response.json();
if (result.status === 200) {
message.success(t('template.update_success'));
showEditModal.value = false;
await loadTemplates();
} else {
message.error(result.message || t('template.update_failed'));
}
} catch (error) {
console.error('更新模板失败:', error);
message.error(t('template.update_failed'));
}
}
// 导出模板
async function exportTemplate(templateId: string) {
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
// 重置进度状态
exportProgress.value = 0;
exportLoading.value = true;
showExportProgress.value = true;
// 发送导出请求
const response = await fetch(`http://${apiHost}:${apiPort}/templates/${templateId}/export`);
if (response.ok) {
// 获取文件名
const contentDisposition = response.headers.get('content-disposition');
let fileName = 'template.zip';
if (contentDisposition) {
const matches = /filename="([^"]+)"/.exec(contentDisposition);
if (matches && matches[1]) {
fileName = matches[1];
}
}
// 获取文件大小
const contentLength = response.headers.get('content-length');
const totalSize = contentLength ? parseInt(contentLength) : 0;
// 创建读取器
const reader = response.body?.getReader();
if (!reader) {
throw new Error('无法读取响应体');
}
// 存储数据
const chunks: Uint8Array[] = [];
let loadedSize = 0;
// 读取数据并更新进度
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
chunks.push(value);
loadedSize += value.length;
if (totalSize > 0) {
exportProgress.value = Math.round((loadedSize / totalSize) * 100);
}
}
}
// 合并数据
const blob = new Blob(chunks);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
message.success(t('home.template_export_success'));
} else {
message.error(t('home.template_export_failed'));
}
} catch (error) {
console.error('导出模板失败:', error);
message.error(t('home.template_export_failed'));
} finally {
// 重置状态
exportLoading.value = false;
showExportProgress.value = false;
exportProgress.value = 0;
}
}
// 导入模板
function importTemplate(options: any) {
const { file, onSuccess, onError } = options;
// 重置进度状态
importProgress.value = 0;
importLoading.value = true;
showImportProgress.value = true;
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
// 监听上传进度
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
importProgress.value = percentComplete;
}
});
// 监听完成
xhr.addEventListener('load', async () => {
try {
const result = JSON.parse(xhr.responseText);
if (result.status === 200) {
message.success(t('home.template_import_success'));
// 重新加载模板列表
await loadTemplates();
if (onSuccess) onSuccess(result);
} else {
message.error(t('home.template_import_failed'));
if (onError) onError(result);
}
} catch (error) {
console.error('导入模板失败:', error);
message.error(t('home.template_import_failed'));
if (onError) onError(error);
} finally {
// 重置状态
importLoading.value = false;
showImportProgress.value = false;
importProgress.value = 0;
}
});
// 监听错误
xhr.addEventListener('error', (error) => {
console.error('导入模板失败:', error);
message.error(t('home.template_import_failed'));
if (onError) onError(error);
// 重置状态
importLoading.value = false;
showImportProgress.value = false;
importProgress.value = 0;
});
// 发送请求
xhr.open('POST', `http://${apiHost}:${apiPort}/templates/import`);
xhr.send(formData);
}
// 加载模板商店数据
async function loadStoreTemplates() {
storeLoading.value = true;
try {
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
const response = await fetch(`http://${apiHost}:${apiPort}/templates/store`);
const result = await response.json();
if (result.status === 200 && result.data && Array.isArray(result.data.templates)) {
storeTemplates.value = result.data.templates;
} else {
message.error(t('template.store_load_failed'));
}
} catch (error) {
console.error('加载模板商店失败:', error);
message.error(t('template.store_load_failed'));
} finally {
storeLoading.value = false;
}
}
// 下载并安装模板
async function downloadAndInstallTemplate(template: StoreTemplate) {
// 重置进度状态
downloadProgress.value = 0;
downloadLoading.value = true;
showDownloadProgress.value = true;
const apiHost = import.meta.env.VITE_API_HOST || 'localhost';
const apiPort = import.meta.env.VITE_API_PORT || '37019';
try {
// 测试所有下载链接的速度
console.log('正在测试下载链接速度...');
const fastestUrl = await testDownloadSpeed(template.downloadUrls);
console.log('选择最快的下载链接:', fastestUrl);
// 创建一个唯一的ID用于SSE连接
const requestId = Math.random().toString(36).substring(2, 10);
const notificationKey = `download-progress-${requestId}`;
// 检查是否有未完成的下载
const existingState = downloadStates.value.get(template.id);
const resumeFrom = existingState ? existingState.downloadedSize : 0;
// 发送POST请求启动下载
const xhr = new XMLHttpRequest();
xhr.open('POST', `http://${apiHost}:${apiPort}/templates/install-from-url`);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () => {
if (xhr.status === 200) {
// 处理响应
console.log('POST请求成功');
}
};
xhr.onerror = () => {
console.error('POST请求失败');
message.error(t('template.install_failed'));
// 重置状态
downloadLoading.value = false;
showDownloadProgress.value = false;
downloadProgress.value = 0;
};
xhr.send(JSON.stringify({ url: fastestUrl, requestId, resumeFrom }));
// 使用EventSource接收进度更新
const eventSource = new EventSource(`http://${apiHost}:${apiPort}/templates/install-from-url?requestId=${requestId}`);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
console.log('收到SSE消息:', data);
switch (data.type) {
case 'init':
// 初始化信息,包含文件大小
console.log('初始化信息:', data);
// 存储下载状态
downloadStates.value.set(template.id, {
url: fastestUrl,
downloadedSize: data.resumeFrom || 0,
totalSize: data.totalSize || 0
});
break;
case 'progress':
// 进度更新
downloadProgress.value = data.progress;
console.log('进度更新:', data.progress);
// 更新下载状态
const currentState = downloadStates.value.get(template.id);
if (currentState) {
downloadStates.value.set(template.id, {
...currentState,
downloadedSize: data.downloadedSize || 0,
totalSize: data.totalSize || currentState.totalSize
});
}
// 如果用户关闭了进度对话框,在右上角显示进度
if (!showDownloadProgress.value) {
// 使用相同的key更新通知
message.loading({
content: `${t('home.template_download_progress')} ${data.progress}%`,
duration: 0,
key: notificationKey
});
}
break;
case 'complete':
// 下载完成
downloadProgress.value = 100;
console.log('下载完成:', data);
// 关闭通知
message.destroy(notificationKey);
// 清理下载状态
downloadStates.value.delete(template.id);
// 短暂延迟让用户看到100%的进度
setTimeout(async () => {
message.success(t('template.install_success'));
// 重新加载本地模板列表
await loadTemplates();
// 重置状态
downloadLoading.value = false;
showDownloadProgress.value = false;
downloadProgress.value = 0;
// 关闭EventSource
eventSource.close();
console.log('重置状态');
}, 500);
break;
case 'error':
// 错误信息
console.error('下载错误:', data);
// 关闭通知
message.destroy(notificationKey);
message.error(data.message || t('template.install_failed'));
// 重置状态
downloadLoading.value = false;
showDownloadProgress.value = false;
downloadProgress.value = 0;
// 关闭EventSource
eventSource.close();
break;
}
} catch (error) {
console.error('解析SSE消息失败:', error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接错误:', error);
// 关闭通知
message.destroy(notificationKey);
message.error(t('template.install_failed'));
// 重置状态
downloadLoading.value = false;
showDownloadProgress.value = false;
downloadProgress.value = 0;
// 关闭EventSource
eventSource.close();
};
} catch (error) {
console.error('下载模板失败:', error);
message.error(t('template.install_failed'));
// 重置状态
downloadLoading.value = false;
showDownloadProgress.value = false;
downloadProgress.value = 0;
}
}
onMounted(() => {
loadTemplates();
});
</script>
<template>
<div class="tw:h-full tw:w-full tw:overflow-hidden">
<div class="tw:h-full tw:w-full tw:p-6 tw:overflow-y-auto">
<div class="tw:max-w-7xl tw:mx-auto">
<div class="tw:flex tw:justify-between tw:items-center tw:mb-6">
<div>
<h1 class="tw:text-2xl tw:font-bold tw:text-gray-800">{{ t('template.title') }}</h1>
<p class="tw:text-gray-600 tw:mt-1">{{ t('template.description') }}</p>
</div>
<div class="tw:flex tw:gap-2">
<a-upload
name="file"
:show-upload-list="false"
:custom-request="importTemplate"
>
<a-button type="default" class="tw:flex tw:items-center tw:gap-2">
<UploadOutlined />
{{ t('home.template_import_title') }}
</a-button>
</a-upload>
<a-button type="primary" @click="openCreateModal" class="tw:flex tw:items-center tw:gap-2">
<PlusOutlined />
{{ t('template.create_button') }}
</a-button>
</div>
</div>
<!-- 标签页切换 -->
<a-tabs v-model:activeKey="activeTab" class="tw:mb-6" @change="(key: string) => {
if (key === 'store') {
loadStoreTemplates();
}
}">
<a-tab-pane key="local" :tab="t('template.local_templates')"></a-tab-pane>
<a-tab-pane key="store" :tab="t('template.template_store')"></a-tab-pane>
</a-tabs>
<!-- 本地模板 -->
<a-spin v-if="activeTab === 'local'" :spinning="loading">
<div v-if="templates.length === 0 && !loading" class="tw:text-center tw:py-16 tw:text-gray-500">
<FolderOutlined style="font-size: 64px; margin-bottom: 16px;" />
<p class="tw:text-lg">{{ t('template.empty') }}</p>
<p class="tw:text-sm tw:mt-2">{{ t('template.empty_hint') }}</p>
</div>
<div v-else class="tw:grid tw:grid-cols-1 md:tw:grid-cols-2 lg:tw:grid-cols-3 tw:gap-4">
<div
v-for="template in templates"
:key="template.id"
class="tw:bg-white tw:rounded-lg tw:shadow-md tw:p-5 tw:h-48 tw:flex tw:flex-col tw:border tw:border-gray-200 tw:transition-all tw:duration-300 hover:tw:shadow-lg hover:tw:border-blue-300"
>
<div class="tw:flex-1 tw:overflow-hidden">
<div class="tw:flex tw:justify-between tw:items-start tw:mb-2">
<h3 class="tw:text-lg tw:font-semibold tw:truncate tw:flex-1 tw:mr-2">{{ template.metadata.name }}</h3>
<a-tag color="blue" size="small">{{ template.metadata.version }}</a-tag>
</div>
<p class="tw:text-sm tw:text-gray-600 tw:line-clamp-2 tw:mb-3">{{ template.metadata.description }}</p>
<div class="tw:flex tw:justify-between tw:text-xs tw:text-gray-500">
<span>{{ t('template.author') }}: {{ template.metadata.author }}</span>
<span>{{ template.metadata.created }}</span>
</div>
</div>
<div class="tw:flex tw:justify-between tw:items-center tw:mt-4 tw:pt-4 tw:border-t tw:border-gray-100">
<a-button size="small" @click="openTemplateFolder(template)">
<div class="tw:flex tw:items-center tw:gap-1">
<FolderOutlined />
<span>{{ t('template.open_folder') }}</span>
</div>
</a-button>
<div class="tw:flex tw:gap-2">
<a-button size="small" @click="exportTemplate(template.id)">
<div class="tw:flex tw:items-center tw:gap-1">
<DownloadOutlined />
<span>{{ t('home.template_export_button') }}</span>
</div>
</a-button>
<a-button size="small" @click="openEditModal(template)">
<div class="tw:flex tw:items-center tw:gap-1">
<EditOutlined />
<span>{{ t('template.edit_button') }}</span>
</div>
</a-button>
<a-button size="small" danger @click="openDeleteModal(template)">
<div class="tw:flex tw:items-center tw:gap-1">
<DeleteOutlined />
<span>{{ t('template.delete_button') }}</span>
</div>
</a-button>
</div>
</div>
</div>
</div>
</a-spin>
<!-- 模板商店 -->
<a-spin v-if="activeTab === 'store'" :spinning="storeLoading">
<div v-if="storeTemplates.length === 0 && !storeLoading" class="tw:text-center tw:py-16 tw:text-gray-500">
<DownloadOutlined style="font-size: 64px; margin-bottom: 16px;" />
<p class="tw:text-lg">{{ t('template.store_empty') }}</p>
<p class="tw:text-sm tw:mt-2">{{ t('template.store_empty_hint') }}</p>
</div>
<div v-else class="tw:grid tw:grid-cols-1 md:tw:grid-cols-2 lg:tw:grid-cols-3 tw:gap-4">
<div
v-for="template in storeTemplates"
:key="template.id"
class="tw:bg-white tw:rounded-lg tw:shadow-md tw:p-5 tw:h-48 tw:flex tw:flex-col tw:border tw:border-gray-200 tw:transition-all tw:duration-300 hover:tw:shadow-lg hover:tw:border-blue-300"
>
<div class="tw:flex-1 tw:overflow-hidden">
<div class="tw:flex tw:justify-between tw:items-start tw:mb-2">
<h3 class="tw:text-lg tw:font-semibold tw:truncate tw:flex-1 tw:mr-2">{{ template.name }}</h3>
<a-tag color="green" size="small">{{ template.size }}</a-tag>
</div>
<p class="tw:text-sm tw:text-gray-600 tw:line-clamp-2 tw:mb-3">{{ template.description }}</p>
</div>
<div class="tw:flex tw:justify-end tw:mt-4 tw:pt-4 tw:border-t tw:border-gray-100">
<a-button type="primary" size="small" @click="downloadAndInstallTemplate(template)">
<div class="tw:flex tw:items-center tw:gap-1">
<DownloadOutlined />
<span>{{ t('template.install_button') }}</span>
</div>
</a-button>
</div>
</div>
</div>
</a-spin>
</div>
</div>
<a-modal
v-model:open="showCreateModal"
:title="t('template.create_title')"
@ok="createTemplate"
:ok-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
>
<a-form layout="vertical">
<a-form-item :label="t('template.name')" required>
<a-input v-model:value="newTemplate.name" :placeholder="t('template.name_placeholder')" />
</a-form-item>
<a-form-item :label="t('template.version')">
<a-input v-model:value="newTemplate.version" :placeholder="t('template.version_placeholder')" />
</a-form-item>
<a-form-item :label="t('template.description')">
<a-textarea v-model:value="newTemplate.description" :placeholder="t('template.description_placeholder')" :rows="4" />
</a-form-item>
<a-form-item :label="t('template.author')">
<a-input v-model:value="newTemplate.author" :placeholder="t('template.author_placeholder')" />
</a-form-item>
</a-form>
</a-modal>
<a-modal
v-model:open="showDeleteModal"
:title="t('template.delete_title')"
@ok="confirmDelete"
:ok-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
ok-type="danger"
>
<div class="tw:flex tw:items-start tw:gap-3">
<ExclamationCircleOutlined style="font-size: 24px; color: #ff4d4f;" />
<div>
<p class="tw:mb-2">{{ t('template.delete_confirm', { name: deletingTemplate?.metadata.name }) }}</p>
<p class="tw:text-sm tw:text-gray-500">{{ t('template.delete_warning') }}</p>
</div>
</div>
</a-modal>
<a-modal
v-model:open="showEditModal"
:title="t('template.edit_title')"
@ok="updateTemplate"
:ok-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
>
<a-form layout="vertical">
<a-form-item :label="t('template.name')" required>
<a-input v-model:value="newTemplate.name" :placeholder="t('template.name_placeholder')" />
</a-form-item>
<a-form-item :label="t('template.version')">
<a-input v-model:value="newTemplate.version" :placeholder="t('template.version_placeholder')" />
</a-form-item>
<a-form-item :label="t('template.description')">
<a-textarea v-model:value="newTemplate.description" :placeholder="t('template.description_placeholder')" :rows="4" />
</a-form-item>
<a-form-item :label="t('template.author')">
<a-input v-model:value="newTemplate.author" :placeholder="t('template.author_placeholder')" />
</a-form-item>
</a-form>
</a-modal>
<!-- 导出进度对话框 -->
<a-modal
v-model:open="showExportProgress"
:title="t('home.template_export_button')"
:footer="null"
:closable="false"
>
<div class="tw:py-4">
<p class="tw:text-center tw:mb-4">{{ t('home.template_export_progress') }}</p>
<a-progress
:percent="exportProgress"
:status="exportLoading ? 'active' : 'success'"
:stroke-width="10"
/>
</div>
</a-modal>
<!-- 导入进度对话框 -->
<a-modal
v-model:open="showImportProgress"
:title="t('home.template_import_title')"
:footer="null"
:closable="false"
>
<div class="tw:py-4">
<p class="tw:text-center tw:mb-4">{{ t('home.template_import_progress') }}</p>
<a-progress
:percent="importProgress"
:status="importLoading ? 'active' : 'success'"
:stroke-width="10"
/>
<p class="tw:text-center tw:mt-4 tw:text-gray-500">{{ importProgress }}%</p>
</div>
</a-modal>
<!-- 下载进度对话框 -->
<a-modal
v-model:open="showDownloadProgress"
:title="t('template.install_button')"
:footer="null"
:closable="false"
>
<div class="tw:py-4">
<p class="tw:text-center tw:mb-4">{{ t('home.template_download_progress') }}</p>
<a-progress
:percent="downloadProgress"
:status="downloadLoading ? 'active' : 'success'"
:stroke-width="10"
/>
</div>
</a-modal>
</div>
</template>

13
front/template_stor.json Normal file
View File

@@ -0,0 +1,13 @@
{
"templates": [
{
"id": "Fabric-1.20.1-dex",
"name": "Fabric-1.20.1-dex",
"description": "Fabric1.20.1版本",
"size": "108.6MB",
"downloadUrls": [
"https://pan.xcclyc.cn/f/pjfR/xccdex-official-fabric-1.20.1-0.18.4.zip"
]
}
]
}

29
front/tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Path mapping */
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

10
front/tsconfig.node.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

43
front/vite.config.ts Normal file
View File

@@ -0,0 +1,43 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";
import { resolve } from "path";
// @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST;
// https://vite.dev/config/
export default defineConfig(async () => ({
plugins: [vue(),
tailwindcss()
],
// 路径别名配置
resolve: {
alias: {
"@": resolve(__dirname, "src")
}
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent Vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 9888,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 1421,
}
: undefined,
watch: {
// 3. tell Vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
}));