项目迁移
This commit is contained in:
475
front/src/components/WebSocketHandler.vue
Normal file
475
front/src/components/WebSocketHandler.vue
Normal 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>
|
||||
Reference in New Issue
Block a user