refactor: 重构 GUI 框架为 Wails
- 添加 Go 后端实现,包括配置管理、文件上传逻辑和 Wails 应用接口 - 实现前端 Vue 界面,提供服务器配置、目录选择、上传控制等功能 - 集成 Element Plus 组件库构建用户界面 - 添加文件上传进度显示和实时日志输出功能 - 实现后台文件监控和上传任务管理 - 配置 Wails 框架支持前后端交互 - 更新项目依赖,移除 Fyne 框架,集成 Wails v2 - 添加项目配置文件管理和自动保存功能
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref, nextTick, watch} from 'vue'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {ElMessageBox} from 'element-plus'
|
||||
import {SelectPath, GetConfig, WriteConfig, StartUpload, StopUpload} from '../wailsjs/go/main/App';
|
||||
import {config} from "../wailsjs/go/models.ts";
|
||||
import Config = config.Config;
|
||||
import {EventsOn, LogPrint} from "../wailsjs/runtime";
|
||||
|
||||
const serverUrl = ref('')
|
||||
const token = ref('')
|
||||
const checkDir = ref('')
|
||||
const concurrentFiles = ref(1)
|
||||
const uploadThreads = ref(1)
|
||||
|
||||
const progress = ref(0)
|
||||
const isRunning = ref(false)
|
||||
const logOutput = ref<string[]>([])
|
||||
const logContentRef = ref<HTMLElement>()
|
||||
|
||||
interface FileProgress {
|
||||
name: string
|
||||
uploaded: number
|
||||
total: number
|
||||
percentage: number
|
||||
}
|
||||
|
||||
const progressList = ref<FileProgress[]>([
|
||||
// {name: '测试文件1.txt', uploaded: 100, total: 500, percentage: 20},
|
||||
])
|
||||
|
||||
const addLog = (msg: string) => {
|
||||
logOutput.value.push(`[${new Date().toLocaleString()}]` + msg)
|
||||
nextTick(() => {
|
||||
if (logContentRef.value) {
|
||||
logContentRef.value.scrollTop = logContentRef.value.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const selectDirectory = () => {
|
||||
// ElMessage.info('请手动输入检测目录路径')
|
||||
SelectPath().then((path) => {
|
||||
checkDir.value = path
|
||||
})
|
||||
}
|
||||
|
||||
const startRun = () => {
|
||||
if (!serverUrl.value) {
|
||||
ElMessage.warning('请输入服务器地址')
|
||||
return
|
||||
}
|
||||
if (!token.value) {
|
||||
ElMessage.warning('请输入Token')
|
||||
return
|
||||
}
|
||||
if (!checkDir.value) {
|
||||
ElMessage.warning('请选择检测目录')
|
||||
return
|
||||
}
|
||||
isRunning.value = true
|
||||
progress.value = 0
|
||||
addLog("===============================================")
|
||||
// addLog(`开始运行...`)
|
||||
addLog(`服务器: ${serverUrl.value}`)
|
||||
addLog(`检测目录: ${checkDir.value}`)
|
||||
addLog(`同时处理文件数: ${concurrentFiles.value}`)
|
||||
addLog(`单文件上传线程: ${uploadThreads.value}`)
|
||||
addLog("===============================================")
|
||||
StartUpload()
|
||||
}
|
||||
|
||||
const stopRun = () => {
|
||||
if (isRunning.value) {
|
||||
isRunning.value = false
|
||||
addLog(`正在停止运行`)
|
||||
StopUpload().then(() => {
|
||||
ElMessage.info('已停止运行')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const clearLog = () => {
|
||||
ElMessageBox.confirm('确定要清除日志吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
logOutput.value = []
|
||||
ElMessage.success('日志已清除')
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
try {
|
||||
GetConfig().then((config: Config) => {
|
||||
serverUrl.value = config.url
|
||||
token.value = config.token
|
||||
checkDir.value = config.check_dir
|
||||
concurrentFiles.value = config.handle_file_count
|
||||
uploadThreads.value = config.thread_count
|
||||
|
||||
LogPrint(`[${new Date().toLocaleString()}] 配置已加载`)
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
watch(serverUrl, () => {
|
||||
WriteConfig("url", serverUrl.value)
|
||||
})
|
||||
watch(token, () => {
|
||||
WriteConfig("token", token.value)
|
||||
})
|
||||
watch(checkDir, () => {
|
||||
WriteConfig("check-dir", checkDir.value)
|
||||
})
|
||||
watch(concurrentFiles, () => {
|
||||
WriteConfig("handle-file-count", concurrentFiles.value)
|
||||
})
|
||||
watch(uploadThreads, () => {
|
||||
WriteConfig("thread-count", uploadThreads.value)
|
||||
})
|
||||
|
||||
EventsOn("log", (msg) => {
|
||||
addLog(msg)
|
||||
})
|
||||
EventsOn("progress", (progress) => {
|
||||
progressList.value = progress
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="left-panel">
|
||||
<div class="form-item">
|
||||
<label>服务器地址</label>
|
||||
<el-input v-model="serverUrl" placeholder="请输入服务器地址" :disabled="isRunning"/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>Token</label>
|
||||
<el-input v-model="token" placeholder="请输入Token" :disabled="isRunning"/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>检测目录</label>
|
||||
<div class="dir-input">
|
||||
<el-input v-model="checkDir" placeholder="请选择检测目录" :disabled="isRunning"/>
|
||||
<el-button @click="selectDirectory" :disabled="isRunning">选择目录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>同时处理文件数</label>
|
||||
<el-input-number v-model="concurrentFiles" :min="1" :max="100" :disabled="isRunning"/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>单文件上传线程</label>
|
||||
<el-input-number v-model="uploadThreads" :min="1" :max="100" :disabled="isRunning"/>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<label>上传进度</label>
|
||||
<div class="progress-list">
|
||||
<div v-for="(item, index) in progressList" :key="index" class="progress-item">
|
||||
<span class="file-name">{{ item.name }}</span>
|
||||
<el-progress :percentage="item.percentage" :status="item.percentage === 100 ? 'success' : undefined"/>
|
||||
<span class="progress-text">{{ item.uploaded }}/{{ item.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<el-button type="primary" @click="startRun" :disabled="isRunning">开始运行</el-button>
|
||||
<el-button type="danger" @click="stopRun" :disabled="!isRunning">停止运行</el-button>
|
||||
<el-button @click="clearLog">清除日志</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="log-header">日志输出</div>
|
||||
<div class="log-content" ref="logContentRef">
|
||||
<div v-for="(log, index) in logOutput" :key="index" class="log-line">{{ log }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
height: calc(100vh - 40px);
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 350px;
|
||||
min-width: 300px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-item label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dir-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dir-input .el-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.progress-list {
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.progress-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
width: 130px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress-item .el-progress {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
width: 75px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
flex: 1;
|
||||
padding: 5px 5px;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
background: #1e1e1e;
|
||||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
color: #dcdfe4;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-line:hover {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import './style.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,27 @@
|
||||
html {
|
||||
/*background-color: rgba(27, 38, 54, 1);*/
|
||||
/*text-align: center;*/
|
||||
/*color: white;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
/*color: white;*/
|
||||
/*font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",*/
|
||||
/*"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",*/
|
||||
/*sans-serif;*/
|
||||
}
|
||||
|
||||
/*@font-face {*/
|
||||
/* font-family: "Nunito";*/
|
||||
/* font-style: normal;*/
|
||||
/* font-weight: 400;*/
|
||||
/* !*src: local(""),*!*/
|
||||
/* !*url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");*!*/
|
||||
/*}*/
|
||||
|
||||
/*#app {*/
|
||||
/* !*height: 100vh;*!*/
|
||||
/* !*text-align: center;*!*/
|
||||
/*}*/
|
||||
Reference in New Issue
Block a user