chore(AI):前端逻辑优化

This commit is contained in:
Tianpao
2025-12-27 15:33:40 +08:00
parent 419c40c794
commit 0fd727ec13
3 changed files with 309 additions and 241 deletions

View File

@@ -4,47 +4,46 @@ import { MenuProps, message } from 'ant-design-vue';
import { SettingOutlined, UserOutlined, WindowsOutlined } from '@ant-design/icons-vue';
import { useRouter } from 'vue-router';
import * as shell from '@tauri-apps/plugin-shell';
import { invoke } from "@tauri-apps/api/core";
async function contant() {
await invoke("open_url", { url: "https://space.bilibili.com/1728953419" })
// 打开作者B站空间
async function openAuthorBilibili() {
await invoke("open_url", { url: "https://space.bilibili.com/1728953419" });
}
//屏蔽右键菜单
document.oncontextmenu = function (event: any) {
// 屏蔽右键菜单(输入框和文本域除外)
document.oncontextmenu = (event: any) => {
try {
var the = event.srcElement
if (
!(
(the.tagName == 'INPUT' && the.type.toLowerCase() == 'text') ||
the.tagName == 'TEXTAREA'
)
) {
return false
}
return true
} catch (e) {
return false
const target = event.srcElement;
const isInput = target.tagName === 'INPUT' && target.type.toLowerCase() === 'text';
const isTextarea = target.tagName === 'TEXTAREA';
return isInput || isTextarea;
} catch {
return false;
}
}
};
/* 启动后端 */
message.loading("DeEarthX.Core启动中此过程中请勿执行任何操作......").then(() => {
shell.Command.create("core").spawn().then(() => {
fetch("http://localhost:37019/", { method: "GET" }).catch((e) => {
router.push('/error')
}).then(() => {
message.success("DeEarthX.Core 启动成功")
})
console.log(`DeEarthX V3 Core`)
}).catch((e) => {
console.log(e)
message.error("DeEarthX.Core 启动失败请检查37019是否被占用")
})
})
const router = useRouter();
const selectedKeys = ref(['main']);
const items: MenuProps['items'] = [
// 启动后端核心服务
message.loading("DeEarthX.Core启动中请勿操作...").then(() => {
shell.Command.create("core").spawn()
.then(() => {
// 检查后端服务是否成功启动
fetch("http://localhost:37019/", { method: "GET" })
.catch(() => router.push('/error'))
.then(() => message.success("DeEarthX.Core 启动成功"));
console.log("DeEarthX V3 Core");
})
.catch((error) => {
console.error(error);
message.error("DeEarthX.Core 启动失败请检查37019端口是否被占用");
});
});
// 导航菜单配置
const selectedKeys = ref<(string | number)[]>(['main']);
const menuItems: MenuProps['items'] = [
{
key: 'main',
icon: h(WindowsOutlined),
@@ -63,31 +62,26 @@ const items: MenuProps['items'] = [
label: '关于',
title: '关于',
}
]
const handleClick: MenuProps['onClick'] = (e) => {
switch (e.key) {
case 'main':
selectedKeys.value[0] = 'main';
router.push('/');
break;
case 'setting':
selectedKeys.value[0] = 'setting';
router.push('/setting');
break;
case 'about':
selectedKeys.value[0] = 'about';
router.push('/about');
break;
default:
break;
}
}
];
// 菜单点击事件处理
const handleMenuClick: MenuProps['onClick'] = (e) => {
selectedKeys.value[0] = e.key;
const routeMap: Record<string, string> = {
main: '/',
setting: '/setting',
about: '/about'
};
const route = routeMap[e.key] || '/';
router.push(route);
};
// 主题配置
const theme = ref({
"token": {
"colorPrimary": "#67eac3",
token: {
colorPrimary: '#67eac3',
}
})
});
</script>
<template>
@@ -96,12 +90,12 @@ const theme = ref({
<a-page-header class="tw:fixed tw:h-16" style="border: 1px solid rgb(235, 237, 240)" title="DeEarthX"
sub-title="V3" :avatar="{ src: './public/dex.png' }">
<template #extra>
<a-button @click="contant">作者B站</a-button>
<a-button @click="openAuthorBilibili">作者B站</a-button>
</template>
</a-page-header>
<div class="tw:flex tw:full tw:h-89/100">
<a-menu id="menu" style="width: 144px;" :selectedKeys="selectedKeys" mode="inline" :items="items"
@click="handleClick" />
<a-menu id="menu" style="width: 144px;" :selectedKeys="selectedKeys" mode="inline" :items="menuItems"
@click="handleMenuClick" />
<RouterView />
</div>
</div>

View File

@@ -1,162 +1,212 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { ref, watch } 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 { sendNotification } from '@tauri-apps/plugin-notification';
import { SelectProps } from 'ant-design-vue/es/vc-select';
interface IWSM {
status: "unzip"|"finish"|"changed"|"downloading"|"error",
result: any
// WebSocket消息类型接口
interface WebSocketMessage {
status: 'unzip' | 'finish' | 'changed' | 'downloading' | 'error';
result: any;
}
/* 进度显示区 */
const disp_steps = ref(false);
const setyps_current = ref(0);
const setps_items: StepsProps['items'] = [{
title: '解压整合包',
description: '解压内容 下载文件'
}, {
title: '筛选模组',
description: 'DeEarthX的心脏'
}, {
title: '下载服务端',
description: '安装模组加载器服务端'
}, {
title: '完成',
description: '一切就绪!'
}]
/* 进度显示区 */
// 进度步骤配置
const showSteps = ref(false);
const currentStep = ref(0);
const stepItems: StepsProps['items'] = [
{ title: '解压整合包', description: '解压内容并下载文件' },
{ title: '筛选模组', description: 'DeEarthX的核心功能' },
{ title: '下载服务端', description: '安装模组加载器服务端' },
{ title: '完成', description: '一切就绪!' }
];
/* 获取文件区 */
const FileList = ref<UploadFile[]>([]);
const isDisabled = ref(false);
const BtnisDisabled = ref(false);
// 文件上传相关
const uploadedFiles = ref<UploadFile[]>([]);
const uploadDisabled = ref(false);
const startButtonDisabled = ref(false);
// 阻止默认上传行为
function beforeUpload() {
return false;
}
function handleChange(info: UploadChangeParam) {
// 处理文件上传变更
function handleFileChange(info: UploadChangeParam) {
if (!info.file.name?.endsWith('.zip') && !info.file.name?.endsWith('.mrpack')) {
message.error('只能上传.zip和.mrpack文件');
return;
}
isDisabled.value = true; //禁用
uploadDisabled.value = true;
}
function handleDrop(e: DragEvent) {
// 处理文件拖拽(预留功能)
function handleFileDrop(e: DragEvent) {
console.log(e);
}
function handleUpload() {
if (FileList.value.length === 0) {
message.warning('请先拖拽或选择文件')
return
// 重置所有状态
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 javaAvailable = ref(true);
const modeOptions: SelectProps['options'] = [
{ label: '开服模式', value: 'server', disabled: !javaAvailable.value },
{ label: '上传模式', value: 'upload', disabled: false }
];
const selectedMode = ref(javaAvailable.value ? 'server' : 'upload');
// 监听Java可用性变化更新模式选项
watch(javaAvailable, (newValue) => {
modeOptions[0].disabled = !newValue;
if (!newValue && selectedMode.value === 'server') {
selectedMode.value = 'upload';
}
if (!FileList.value[0].originFileObj){
});
// 处理模式选择
function handleModeSelect(value: string) {
selectedMode.value = value;
}
// 进度显示相关
interface ProgressStatus {
status: 'active' | 'success' | 'exception' | 'normal';
percent: number;
display: boolean;
}
const unzipProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
const downloadProgress = ref<ProgressStatus>({ status: 'active', percent: 0, display: true });
// 运行DeEarthX核心功能
async function runDeEarthX(file: Blob) {
message.success('开始制作,请勿切换菜单!');
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch(`http://localhost:37019/start?mode=${selectedMode.value}`, {
method: 'POST',
body: formData
});
await response.json();
setupWebSocket();
} catch (error) {
console.error('请求失败:', error);
message.error('请求后端服务失败');
resetState();
}
}
// 设置WebSocket连接
function setupWebSocket() {
const ws = new WebSocket('ws://localhost:37019/');
ws.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data) as WebSocketMessage;
// 处理不同状态的消息
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;
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
notification.error({ message: '错误', description: '解析服务器消息失败' });
}
});
ws.addEventListener('error', () => {
notification.error({ message: '错误', description: 'WebSocket连接失败' });
resetState();
});
}
// 处理错误消息
function handleError(result: any) {
if (result === 'jini') {
javaAvailable.value = false;
message.error('未在系统变量中找到Java请安装Java否则开服模式将无法使用');
} else {
notification.error({
message: 'DeEarthX.Core 遇到致命错误!',
description: `请将整个窗口截图发在群里\n错误信息${result}`
});
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(`服务端制作完成!共用时${timeSpent}秒!`);
sendNotification({ title: 'DeEarthX V3', body: `服务端制作完成!共用时${timeSpent}秒!` });
// 8秒后自动重置状态
setTimeout(resetState, 8000);
}
// 开始处理文件
function handleStartProcess() {
if (uploadedFiles.value.length === 0) {
message.warning('请先拖拽或选择文件');
return;
}
runDeEarthX(FileList.value[0].originFileObj) //获取文件内容
BtnisDisabled.value = true; //禁用按钮
isDisabled.value = true; //禁用上传
disp_steps.value = true; //开始显示进度条
}
function reactFL() {
FileList.value = [];
isDisabled.value = false;
BtnisDisabled.value = false;
disp_steps.value = false; //关闭进度条
}
/* 获取文件区 */
function runDeEarthX(data: Blob) {
//console.log(data)
message.success("开始制作,请勿切换菜单!");
const fd = new FormData();
fd.append('file', data);
console.log(fd.getAll('file'))
fetch(`http://localhost:37019/start?mode=${Cselect.value}`,{
method:'POST',
body:fd
}).then(async res=>res.json()).then(()=>{
prews()
})
}
const prog = ref({status:"active",percent:0,display:true})
const dprog = ref({status:"active",percent:0,display:true})
const CanO = ref(true);
function prews(){
const ws = new WebSocket('ws://localhost:37019/')
// ws.addEventListener('message',(wsm)=>{
// const _data = JSON.parse(wsm.data) as IWSM
// if (_data.status === "changed") {
// setyps_current.value ++;
// }
// logs.value.push({message:_data.result})
// })
ws.addEventListener('message',(wsm)=>{
const _data = JSON.parse(wsm.data) as IWSM
//console.log(_data)
if (_data.status === "error" && _data.result === "jini") {
CanO.value = false;
message.error("未在系统变量中找到Java请安装Java否则开服模式将无法使用")
}
if (_data.status === "changed") { //状态更改
setyps_current.value ++;
}
if (_data.status === "unzip"){ //解压ZIP
prog.value.percent = Math.round(((_data.result.current / _data.result.total) * 100))
if (_data.result.current === _data.result.total){
prog.value.status = "succees"
setTimeout(()=>{
prog.value.display = false;
},2000)
}
}
if (_data.status === "downloading"){ //下载文件
dprog.value.percent = Math.round((_data.result.index / _data.result.total) * 100)
if(dprog.value.percent === 100){
dprog.value.status = "succees"
setTimeout(()=>{
dprog.value.display = false;
},2000)
}
}
if (_data.status === "finish"){
console.log(_data.result)
const time = Math.round(_data.result / 1000)
setyps_current.value ++;
message.success(`服务端制作完成!共用时${time}秒!`)
sendNotification({ title: 'DeEarthX V3', body: `服务端制作完成!共用时${time}秒!` });
setTimeout(()=>{ //恢复状态
reactFL()
},8*1000)
}
if (_data.status === "error"){
notification.error({
message: "DeEarth.X.Core 遇到了一个致命错误!",
description:`请将整个窗口截图发在群里\n错误信息${_data.result}`
})
}
})
}
/* 选择区 */
const Moptions = ref<SelectProps['options']>([
{
label: '开服模式',
value: 'server',
disabled: CanO.value ? false : true
},
{
label: '上传模式',
value: 'upload',
disabled: false
}
])
const Cselect = ref(CanO ? 'server' : 'upload')
function handleSelect(value: string) {
Cselect.value = value;
const file = uploadedFiles.value[0].originFileObj;
if (!file) return;
runDeEarthX(file);
startButtonDisabled.value = true;
uploadDisabled.value = true;
showSteps.value = true;
}
</script>
<template>
@@ -166,9 +216,9 @@ function handleSelect(value: string) {
<h1 class="tw:text-4xl tw:text-center tw:animate-pulse">DeEarthX</h1>
<h1 class="tw:text-sm tw:text-gray-500 tw:text-center">让开服变成随时随地的事情</h1>
</div>
<a-upload-dragger :disabled="isDisabled" class="tw:w-144 tw:h-48" name="file" action="/" :multiple="false"
:before-upload="beforeUpload" @change="handleChange" @drop="handleDrop" v-model:fileList="FileList"
accept=".zip,.mrpack">
<a-upload-dragger :disabled="uploadDisabled" class="tw:w-144 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>
@@ -179,31 +229,31 @@ function handleSelect(value: string) {
</a-upload-dragger>
<a-select
ref="select"
:options="Moptions"
:value="Cselect"
:options="modeOptions"
:value="selectedMode"
style="width: 120px;margin-top: 32px"
@select="handleSelect"
@select="handleModeSelect"
></a-select>
<a-button :disabled="BtnisDisabled" type="primary" @click="handleUpload" style="margin-top: 6px">
<a-button :disabled="startButtonDisabled" type="primary" @click="handleStartProcess" style="margin-top: 6px">
开始
</a-button>
</div>
<div v-if="disp_steps"
<div v-if="showSteps"
class="tw:fixed tw:bottom-2 tw:ml-4 tw:w-272 tw:h-16 tw:flex tw:justify-center tw:items-center tw:text-sm">
<a-steps :current="setyps_current" :items="setps_items" />
<a-steps :current="currentStep" :items="stepItems" />
</div>
<!-- <div class="tw:absolute tw:bottom-20 tw:right-2 tw:h-16 tw:w-16">
</div> -->
<div v-if="disp_steps" ref="logContainer" class="tw:absolute tw:right-2 tw:bottom-20 tw:h-80 tw:w-56 tw:rounded-xl tw:container tw:overflow-y-auto">
<div v-if="showSteps" ref="logContainer" class="tw:absolute tw:right-2 tw:bottom-20 tw:h-80 tw:w-56 tw:rounded-xl tw:container tw:overflow-y-auto">
<a-card title="制作进度" :bordered="true">
<div v-if="prog.display">
<div v-if="unzipProgress.display">
<h1>解压进度</h1>
<a-progress :percent="prog.percent" :status="prog.status" size="small" />
<a-progress :percent="unzipProgress.percent" :status="unzipProgress.status" size="small" />
</div>
<div v-if="dprog.display">
<div v-if="downloadProgress.display">
<h1>下载进度</h1>
<a-progress :percent="dprog.percent" :status="dprog.status" size="small" />
<a-progress :percent="downloadProgress.percent" :status="downloadProgress.status" size="small" />
</div>
</a-card>
</div>

View File

@@ -1,8 +1,9 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { message } from 'ant-design-vue';
/* Config */
interface IConfig {
// 配置接口定义
interface AppConfig {
mirror: {
bmclapi: boolean;
mcimirror: boolean;
@@ -12,38 +13,61 @@ interface IConfig {
dexpub: boolean;
mixins: boolean;
};
oaf: boolean
oaf: boolean; // 操作完成后打开目录
}
const config = ref<IConfig>({ mirror: { bmclapi: false, mcimirror: false }, filter: { hashes: false, dexpub: false, mixins: false }, oaf: false})
fetch('http://localhost:37019/config/get', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()).then(res => config.value = res)
let first = true;
watch(config, (newv) => { //写入Config
if (!first) {
fetch('http://localhost:37019/config/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(newv)
}).then(res => {
if (res.status === 200) {
message.success('配置已保存')
}
})
// shell.Command.create('core',['writeconfig',JSON.stringify(newv)]).execute().then(()=>{
// message.success('配置已保存')
// })
console.log(newv)
return
// 配置状态
const config = ref<AppConfig>({
mirror: { bmclapi: false, mcimirror: false },
filter: { hashes: false, dexpub: false, mixins: false },
oaf: false
});
// 从后端加载配置
async function loadConfig() {
try {
const response = await fetch('http://localhost:37019/config/get', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
config.value = await response.json();
}
} catch (error) {
console.error('加载配置失败:', error);
message.error('加载配置失败');
}
first = false;
}, { deep: true })
}
// 保存配置到后端
async function saveConfig(newConfig: AppConfig) {
try {
const response = await fetch('http://localhost:37019/config/post', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newConfig)
});
if (response.ok) {
message.success('配置已保存');
}
} catch (error) {
console.error('保存配置失败:', error);
message.error('保存配置失败');
}
}
// 初始化时加载配置
loadConfig();
// 监听配置变化并保存
let isInitialLoad = true;
watch(config, (newValue) => {
if (isInitialLoad) {
isInitialLoad = false;
return;
}
saveConfig(newValue);
}, { deep: true });
</script>
<template>