From 258871082ba189afa06eb1c470517cfcdcb01f54 Mon Sep 17 00:00:00 2001 From: NangInShell <1759890165@qq.com> Date: Tue, 9 Sep 2025 18:51:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?runcommand=20=E5=8A=9F=E8=83=BD=E5=88=92?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/childProcessManager.ts | 4 - src/main/runCommand.ts | 189 ++++++++++++++++++-------------- 2 files changed, 108 insertions(+), 85 deletions(-) diff --git a/src/main/childProcessManager.ts b/src/main/childProcessManager.ts index a53f9dd..1edbf76 100644 --- a/src/main/childProcessManager.ts +++ b/src/main/childProcessManager.ts @@ -29,10 +29,6 @@ function safeUnpipe(): void { vspipe.proc.stdout.unpipe(ffmpeg.proc.stdin) } catch {} - try { - ffmpeg.proc.stdin.end() - } - catch {} } } diff --git a/src/main/runCommand.ts b/src/main/runCommand.ts index 0ff5a0d..117f1df 100644 --- a/src/main/runCommand.ts +++ b/src/main/runCommand.ts @@ -42,13 +42,118 @@ function generate_cmd(taskConfig: TaskConfig, hasAudio: boolean, hasSubtitle: bo return cmd } +// 新增:获取输入视频信息的独立函数 +async function getInputVideoInfo(event: IpcMainEvent, video: string): Promise<{ + hasAudio: boolean + hasSubtitle: boolean + videoStream: any +}> { + const ffprobePath = getExecPath().ffprobe + + const ffprobeCommand = `"${ffprobePath}" -v error -show_streams -of json "${video}"` + const { stdout: probeOut } = await exec(ffprobeCommand) + const metadata = JSON.parse(probeOut) + + const allStreams = metadata.streams || [] + const videoStream = allStreams.find((s: any) => s.codec_type === 'video') + const hasAudio = allStreams.some((s: any) => s.codec_type === 'audio') + const hasSubtitle = allStreams.some((s: any) => s.codec_type === 'subtitle') + + if (videoStream) { + const frameCount = videoStream.nb_frames || '未知' + const frameRate = videoStream.avg_frame_rate || '未知' + const resolution = `${videoStream.width}x${videoStream.height}` + const audioText = hasAudio ? '是' : '否' + const subtitleText = hasSubtitle ? '是' : '否' + + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `正在处理输入视频 ${video} 的信息:\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数(输入): ${frameCount}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率(输入): ${frameRate}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `分辨率(输入): ${resolution}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有音频: ${audioText}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有字幕: ${subtitleText}\n`) + } + + return { + hasAudio, + hasSubtitle, + videoStream, + } +} + +// 新增:获取输出视频信息的独立函数 +async function getOutputVideoInfo(event: IpcMainEvent, vpyPath: string): Promise<{ + width: string + height: string + frames: string + fps: string +}> { + const vspipePath = getExecPath().vspipe + + const info: { + width: string + height: string + frames: string + fps: string + } = { + width: '未知', + height: '未知', + frames: '0', + fps: '0', + } + + await new Promise((resolve, reject) => { + const vspipeInfoProcess = spawn(vspipePath, ['--info', vpyPath]) + addProcess('vspipe', vspipeInfoProcess) + + let vspipeOut = '' // 用于保存 stdout 内容 + // eslint-disable-next-line unused-imports/no-unused-vars + let stderrOut = '' // 用于保存 stderr 内容 + + vspipeInfoProcess.stdout.on('data', (data: Buffer) => { + const str = iconv.decode(data, 'gbk') + vspipeOut += str + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) + }) + + vspipeInfoProcess.stderr.on('data', (data: Buffer) => { + const str = iconv.decode(data, 'gbk') + stderrOut += str + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) + }) + + vspipeInfoProcess.on('close', (code) => { + removeProcess(vspipeInfoProcess) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe info 执行完毕,退出码: ${code}\n`) + + info.width = vspipeOut.match(/Width:\s*(\d+)/)?.[1] || '未知' + info.height = vspipeOut.match(/Height:\s*(\d+)/)?.[1] || '未知' + info.frames = vspipeOut.match(/Frames:\s*(\d+)/)?.[1] || '0' + info.fps = vspipeOut.match(/FPS:\s*([\d/]+)\s*\(([\d.]+) fps\)/)?.[2] || '0' + + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `======= 输出视频信息 =======\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `宽: ${info.width}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `高: ${info.height}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数: ${info.frames}\n`) + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率: ${info.fps}\n`) + resolve() + }) + + vspipeInfoProcess.on('error', (err) => { + event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe 执行出错: ${err.message}\n`) + reject(err) + }) + }) + + return info +} + export async function runCommand(event: IpcMainEvent, taskConfig: TaskConfig): Promise { const vpyContent = taskConfig.vpyContent const ffmpegCMD = taskConfig.ffmpegCMD const vspipePath = getExecPath().vspipe const ffmpegPath = getExecPath().ffmpeg - const ffprobePath = getExecPath().ffprobe const videos = Array.isArray(taskConfig.fileList) ? taskConfig.fileList : [] @@ -62,99 +167,21 @@ export async function runCommand(event: IpcMainEvent, taskConfig: TaskConfig): P } try { // ========== 1. 获取输入视频信息 ========== - const ffprobeCommand = `"${ffprobePath}" -v error -show_streams -of json "${video}"` - const { stdout: probeOut } = await exec(ffprobeCommand) - const metadata = JSON.parse(probeOut) - - const allStreams = metadata.streams || [] - const videoStream = allStreams.find((s: any) => s.codec_type === 'video') - const hasAudio = allStreams.some((s: any) => s.codec_type === 'audio') - const hasSubtitle = allStreams.some((s: any) => s.codec_type === 'subtitle') - - if (videoStream) { - const frameCount = videoStream.nb_frames || '未知' - const frameRate = videoStream.avg_frame_rate || '未知' - const resolution = `${videoStream.width}x${videoStream.height}` - const audioText = hasAudio ? '是' : '否' - const subtitleText = hasSubtitle ? '是' : '否' // 字幕信息 - - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `正在处理输入视频 ${video} 的信息:\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数(输入): ${frameCount}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率(输入): ${frameRate}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `分辨率(输入): ${resolution}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有音频: ${audioText}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `是否含有字幕: ${subtitleText}\n`) - } + const { hasAudio, hasSubtitle } = await getInputVideoInfo(event, video) // ========== 2. 生成 vpy 文件 ========== - // 生成唯一 vpy 路径 const baseName = path.basename(video, path.extname(video)) const vpyPath = getGenVpyPath(taskConfig, baseName) await writeVpyFile(null, vpyPath, vpyContent, video) // ========== 3. 获取输出视频信息 ========== - let info: { - width: string - height: string - frames: string - fps: string - } = { - width: '未知', - height: '未知', - frames: '0', - fps: '0', - } - await new Promise((resolve, reject) => { - const vspipeInfoProcess = spawn(vspipePath, ['--info', vpyPath]) - addProcess('vspipe', vspipeInfoProcess) - - let vspipeOut = '' // 用于保存 stdout 内容 - // eslint-disable-next-line unused-imports/no-unused-vars - let stderrOut = '' // 用于保存 stderr 内容 - - vspipeInfoProcess.stdout.on('data', (data: Buffer) => { - const str = iconv.decode(data, 'gbk') - vspipeOut += str - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) - }) - - vspipeInfoProcess.stderr.on('data', (data: Buffer) => { - const str = iconv.decode(data, 'gbk') - stderrOut += str - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `${str}`) - }) - - vspipeInfoProcess.on('close', (code) => { - removeProcess(vspipeInfoProcess) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe info 执行完毕,退出码: ${code}\n`)/////// - - info = { - width: vspipeOut.match(/Width:\s*(\d+)/)?.[1] || '未知', - height: vspipeOut.match(/Height:\s*(\d+)/)?.[1] || '未知', - frames: vspipeOut.match(/Frames:\s*(\d+)/)?.[1] || '0', - fps: vspipeOut.match(/FPS:\s*([\d/]+)\s*\(([\d.]+) fps\)/)?.[2] || '0', - } - - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `======= 输出视频信息 =======\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `宽: ${info.width}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `高: ${info.height}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧数: ${info.frames}\n`) - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `帧率: ${info.fps}\n`) - resolve() - }) - - vspipeInfoProcess.on('error', (err) => { - event.sender.send(IpcChannelOn.FFMPEG_OUTPUT, `vspipe 执行出错: ${err.message}\n`) - reject(err) - }) - }) + const info = await getOutputVideoInfo(event, vpyPath) // ========== 4. 构建渲染命令 ========== const vspipeArgs = ffmpegCMD[0].replace(MagicStr.VPY_PATH, vpyPath) const ffmpegMajorArgs = ffmpegCMD[1] const ffmpegMinorArgs = ffmpegCMD[2] const ffmpeg_audio_sub_Args = generate_cmd(taskConfig, hasAudio, hasSubtitle) - const ffmpegArgs = ffmpegMajorArgs.replace(MagicStr.VIDEO_PATH, video) + ffmpeg_audio_sub_Args + ffmpegMinorArgs.replace(MagicStr.VIDEO_NAME, path.join(taskConfig.outputFolder, `${baseName}_enhance`) + taskConfig.videoContainer) const full_cmd = `${`"${vspipePath}" ${vspipeArgs}`} | "${ffmpegPath}" ${ffmpegArgs}` From b496add21acf93b37625e278344cb185bf1f340b Mon Sep 17 00:00:00 2001 From: NangInShell <1759890165@qq.com> Date: Sun, 21 Sep 2025 15:55:01 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/index.ts | 28 +- src/preload/index.ts | 8 +- src/renderer/src/App.vue | 214 +++-- src/renderer/src/components/EnhancePage.vue | 839 ++++++++++---------- src/renderer/src/components/FilterPage.vue | 160 ++-- src/renderer/src/components/HomePage.vue | 58 +- src/renderer/src/components/OutputPage.vue | 501 ++++++------ src/renderer/src/components/PreviewPage.vue | 2 +- src/renderer/src/components/RenderPage.vue | 4 +- src/renderer/src/env.d.ts | 10 + tsconfig.json | 3 +- 11 files changed, 1006 insertions(+), 821 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index f1a39d9..d4aa957 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -12,14 +12,15 @@ import { writeSettingsJson } from './writeFile' function createWindow(): BrowserWindow { const mainWindow = new BrowserWindow({ - width: 900, - height: 700, - minWidth: 900, - minHeight: 670, + width: 1000, + height: 875, + minWidth: 1000, + minHeight: 875, show: false, autoHideMenuBar: true, + frame: false, + resizable: true, icon: nativeImage.createFromPath(appIcon), - title: 'VSET 4.3.6', webPreferences: { preload: path.join(__dirname, '../preload/index.js'), sandbox: false, @@ -27,6 +28,23 @@ function createWindow(): BrowserWindow { }) // ipcMain + + ipcMain.on('window:minimize', () => { + mainWindow?.minimize() + }) + + ipcMain.on('window:maximize', () => { + if (mainWindow?.isMaximized()) { + mainWindow.unmaximize() + } + else { + mainWindow?.maximize() + } + }) + + ipcMain.on('window:close', () => { + mainWindow?.close() + }) ipcMain.on(IpcChannelSend.EXECUTE_COMMAND, runCommand) ipcMain.on(IpcChannelSend.PAUSE, PauseCommand) diff --git a/src/preload/index.ts b/src/preload/index.ts index 8a02264..a9b19a9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,9 +1,13 @@ import { electronAPI } from '@electron-toolkit/preload' -import { contextBridge } from 'electron' +import { contextBridge, ipcRenderer } from 'electron' // Custom APIs for renderer const api = {} - +contextBridge.exposeInMainWorld('electronWindow', { + minimize: () => ipcRenderer.send('window:minimize'), + maximize: () => ipcRenderer.send('window:maximize'), + close: () => ipcRenderer.send('window:close'), +}) // Use `contextBridge` APIs to expose Electron APIs to // renderer only if context isolation is enabled, otherwise // just add to the DOM global. diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 1b7c0c0..7b4ad56 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -2,13 +2,16 @@ import type { MenuOption } from 'naive-ui' import type { Component, VNode } from 'vue' import { + CloseOutline as Close, ConstructOutline as enhanceIcon, CameraOutline as filterIcon, BookOutline as homeIcon, AddCircleOutline as inputIcon, ColorFillOutline as outputIcon, GlassesOutline as PreviewIcon, + RemoveOutline as Remove, SendOutline as renderingIcon, + SquareOutline as Square, } from '@vicons/ionicons5' import { NIcon } from 'naive-ui' import { defineComponent, h, ref } from 'vue' @@ -19,69 +22,46 @@ function renderIcon(icon: Component): () => VNode { } const menuOptions: MenuOption[] = [ - { - whateverLabel: '主页', - whateverKey: 'home', - icon: renderIcon(homeIcon), - path: '/', - }, - - { - whateverLabel: '输入', - whateverKey: 'input', - icon: renderIcon(inputIcon), - path: '/input', - }, - - { - whateverLabel: '增强', - whateverKey: 'enhance', - icon: renderIcon(enhanceIcon), - path: '/enhance', - }, - - { - whateverLabel: '滤镜', - whateverKey: 'filter', - icon: renderIcon(filterIcon), - path: '/filter', - }, - - { - whateverLabel: '输出', - whateverKey: 'output', - icon: renderIcon(outputIcon), - path: '/output', - }, - - { - whateverLabel: '渲染', - whateverKey: 'render', - icon: renderIcon(renderingIcon), - path: '/render', - }, - { - whateverLabel: '预览', - whateverKey: 'preview', - icon: renderIcon(PreviewIcon), - path: '/preview', - }, - + { whateverLabel: '主页', whateverKey: 'home', icon: renderIcon(homeIcon), path: '/' }, + { whateverLabel: '输入', whateverKey: 'input', icon: renderIcon(inputIcon), path: '/input' }, + { whateverLabel: '增强', whateverKey: 'enhance', icon: renderIcon(enhanceIcon), path: '/enhance' }, + { whateverLabel: '滤镜', whateverKey: 'filter', icon: renderIcon(filterIcon), path: '/filter' }, + { whateverLabel: '输出', whateverKey: 'output', icon: renderIcon(outputIcon), path: '/output' }, + { whateverLabel: '渲染', whateverKey: 'render', icon: renderIcon(renderingIcon), path: '/render' }, + { whateverLabel: '预览', whateverKey: 'preview', icon: renderIcon(PreviewIcon), path: '/preview' }, ] export default defineComponent({ setup() { const router = useRouter() + const collapsed = ref(true) + const onMenuChange = (value: string): void => { router.push(value) } + // 添加折叠状态切换处理函数 + const handleCollapseChange = (isCollapsed: boolean) => { + collapsed.value = isCollapsed + } + + const handleMinimize = () => window.electronWindow?.minimize() + const handleMaximize = () => window.electronWindow?.maximize() + const handleClose = () => window.electronWindow?.close() + router.push('/home') return { - collapsed: ref(true), + collapsed, menuOptions, onMenuChange, + handleCollapseChange, // 导出折叠处理函数 + handleMinimize, + handleMaximize, + handleClose, + Remove, + Square, + Close, } }, }) @@ -90,17 +70,38 @@ export default defineComponent({ diff --git a/src/renderer/src/components/EnhancePage.vue b/src/renderer/src/components/EnhancePage.vue index 6555366..43fd4da 100644 --- a/src/renderer/src/components/EnhancePage.vue +++ b/src/renderer/src/components/EnhancePage.vue @@ -80,170 +80,178 @@ function ShowVfiExtra(): void {
-
-
- -
-
- -
-
- -
-
- 超分算法 - - - -
- -
-
- 推理方式(Real_cugan) - +
+
+ - - + active-text="启用超分" + inactive-text="关闭超分" + />
- -
- 超分模型(Real_cugan) - + - - + active-text="启动半精度" + inactive-text="关闭半精度" + disabled + />
+
+ +
+
- 切割块数量(Real_cugan) + 超分算法
+
-
- 强度参数(Real_cugan) - -
+
+ +
+ 推理方式(Real_cugan) + + + +
+ +
+ 超分模型(Real_cugan) + + + +
+ +
+ 切割块数量(Real_cugan) + + + +
+ +
+ 强度参数(Real_cugan) + +
+
-
- 推理方式(Real_esrgan) - - - -
- -
- 超分模型(Real_esrgan) - - - -
- -
- 放大倍数(Real_esrgan) - - - -
- -
- 切割块数量(Real_esrgan) - - - -
+ +
+ 推理方式(Real_esrgan) + + + +
+ +
+ 超分模型(Real_esrgan) + + + +
+ +
+ 放大倍数(Real_esrgan) + + + +
+ +
+ 切割块数量(Real_esrgan) + + + +
+
-
- 推理方式(Waifu2x) - - - -
- -
- 超分模型(Waifu2x) - - - -
- -
- 切割块数量(Waifu2x) - - - -
+ +
+ 推理方式(Waifu2x) + + + +
+ +
+ 超分模型(Waifu2x) + + + +
+ +
+ 切割块数量(Waifu2x) + + + +
+
-
- 超分模型(SR_ExtraModel) - - - -
- -
- 推理方式(SR_ExtraModel) - - - -
-
- -
- - Extra - - - - 通用 - -
- num_streams - - - -
- - - - TensorRt -
- cuda_graph - +
+ 超分模型(SR_ExtraModel) + + -
- - - - -
-
-
- - - -
-
-
- -
-
- -
-
- -
-
- 补帧算法 - - - + +
+ +
+ 推理方式(SR_ExtraModel) + + + +
+
-
-
- 推理方式(rife) - - - -
- -
- 补帧模型(rife) - - - -
- -
- 光流尺度(rife) - - - -
- -
- 目标帧率(rife) - -
- -
- 转场阈值(rife) - -
- -
- Ensemble(rife) - - - 使用 - - - 关闭 - - -
- -
+
+ Extra - - + + 通用
num_streams cuda_graph + +
+
+
+ + + +
+ +
+
+ +
+
+ +
+
+
+ +
+ +
+ 补帧算法 + + + +
+
+ +
+ +
+ 推理方式(rife) + + + +
+ +
+ 补帧模型(rife) + + + +
+ +
+ 光流尺度(rife) + + + +
+ +
+ 目标帧率(rife) + +
+ +
+ 转场阈值(rife) + +
+ +
+ Ensemble(rife) + + + 使用 + + + 关闭 + + +
+
+ +
+ + + Extra + + + + 通用 + +
+ num_streams + + + +
+ + + + TensorRt +
+ cuda_graph + +
+ + +
+
+
@@ -623,6 +645,7 @@ function ShowVfiExtra(): void { display: flex; align-items: center; flex-basis: calc(100% - 20px); + margin-bottom: 16px; } .slider-demo-block .demonstration { @@ -635,6 +658,10 @@ function ShowVfiExtra(): void { white-space: nowrap; margin-bottom: 0; } +.slider-demo-block:last-child { + margin-bottom: 0; + padding-bottom: 0; +} .slider-demo-block .demonstration + .el-slider { flex: 0 0 70%; } @@ -642,7 +669,7 @@ function ShowVfiExtra(): void { .flex-container { display: flex; flex-direction: column; /* 设置为垂直排列 */ - gap: 15px; /* 可选项,用于设置组件之间的间隔 */ + gap: 10px; /* 可选项,用于设置组件之间的间隔 */ } /* 修改这里 */ @@ -654,4 +681,14 @@ function ShowVfiExtra(): void { display: flex; justify-content: flex-end; /* 水平方向靠右 */ } + +.system-info-card-Method { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} + +.system-info-card-Para { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} diff --git a/src/renderer/src/components/FilterPage.vue b/src/renderer/src/components/FilterPage.vue index 7153d88..556d040 100644 --- a/src/renderer/src/components/FilterPage.vue +++ b/src/renderer/src/components/FilterPage.vue @@ -28,93 +28,97 @@ const { 预处理(AI渲染前工作)
-
- 视频长宽缩放(前) - -
- -
+ +
+ 视频长宽缩放(前) + +
+ +
-
- 长度: - - 宽度: - +
+ 长度: + + 宽度: + +
-
-
- 视频黑边(左右) -
- 左侧: - - 右侧: - +
+ 视频黑边(左右) +
+ 左侧: + + 右侧: + +
-
-
- 视频黑边(上下) -
- 上边: - - 下边: - +
+ 视频黑边(上下) +
+ 上边: + + 下边: + +
-
+
后处理(AI渲染后工作)
-
- 视频长宽缩放(末) - -
- -
+ +
+ 视频长宽缩放(末) -
- 长度: - - 宽度: - +
+ +
+ +
+ 长度: + + 宽度: + +
-
-
- 视频黑边(左右) -
- 左侧: - - 右侧: - +
+ 视频黑边(左右) +
+ 左侧: + + 右侧: + +
-
-
- 视频黑边(上下) -
- 上边: - - 下边: - +
+ 视频黑边(上下) +
+ 上边: + + 下边: + +
-
+
@@ -122,7 +126,7 @@ const { .flex-container { display: flex; flex-direction: column; - gap: 15px; + gap: 10px; } .slider-demo-block { @@ -130,7 +134,14 @@ const { display: flex; align-items: center; flex-basis: calc(100% - 20px); - gap: 10px; + gap: 16px; + margin-bottom: 16px; + padding-bottom: 10px; +} + +.slider-demo-block:last-child { + margin-bottom: 0; + padding-bottom: 0; } .slider-demo-block .demonstration { @@ -146,4 +157,9 @@ const { margin-left: 40px; margin-right: 10px; } + +.system-info-card { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} diff --git a/src/renderer/src/components/HomePage.vue b/src/renderer/src/components/HomePage.vue index e43326e..8b4c624 100644 --- a/src/renderer/src/components/HomePage.vue +++ b/src/renderer/src/components/HomePage.vue @@ -66,31 +66,33 @@ onUnmounted(() => { > -
- CPU - - {{ CPUInfo }} - -
- -
- GPU - - - -
- -
- 当前时间 - - {{ currentTime }} - -
+ +
+ CPU + + {{ CPUInfo }} + +
+ +
+ GPU + + + +
+ +
+ 当前时间 + + {{ currentTime }} + +
+
@@ -105,6 +107,7 @@ onUnmounted(() => { display: flex; align-items: center; flex-basis: calc(100% - 20px); + margin-bottom: 16px; } .slider-demo-block .demonstration { @@ -123,4 +126,9 @@ onUnmounted(() => { flex-direction: column; gap: 15px; } + +.system-info-card { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} diff --git a/src/renderer/src/components/OutputPage.vue b/src/renderer/src/components/OutputPage.vue index 947aaad..78c5cfe 100644 --- a/src/renderer/src/components/OutputPage.vue +++ b/src/renderer/src/components/OutputPage.vue @@ -62,156 +62,157 @@ async function selectDirectory(): Promise { @@ -321,6 +326,7 @@ async function selectDirectory(): Promise { display: flex; align-items: center; flex-basis: calc(100% - 20px); + margin-bottom: 16px; } .slider-demo-block .demonstration { @@ -336,7 +342,10 @@ async function selectDirectory(): Promise { .slider-demo-block .demonstration + .el-slider { flex: 0 0 70%; } - +.slider-demo-block:last-child { + margin-bottom: 0; + padding-bottom: 0; +} .flex-container { display: flex; flex-direction: column; /* 设置为垂直排列 */ @@ -347,4 +356,8 @@ async function selectDirectory(): Promise { display: flex; justify-content: flex-end; /* 水平方向靠右 */ } +.system-info-card { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 12px; +} diff --git a/src/renderer/src/components/PreviewPage.vue b/src/renderer/src/components/PreviewPage.vue index 2104d57..1275955 100644 --- a/src/renderer/src/components/PreviewPage.vue +++ b/src/renderer/src/components/PreviewPage.vue @@ -145,7 +145,7 @@ onMounted(() => { :max="frameCount" :step="1" :disabled="isRunning" - style="margin-top: 10px; width: 100%;" + style="margin-top: 10px; width: 100%; margin-left: 12px" show-input @change="onFrameChange" /> diff --git a/src/renderer/src/components/RenderPage.vue b/src/renderer/src/components/RenderPage.vue index 232a967..f9a5fa1 100644 --- a/src/renderer/src/components/RenderPage.vue +++ b/src/renderer/src/components/RenderPage.vue @@ -136,9 +136,9 @@ watch(() => logs.value, () => { .flex-container { display: flex; flex-direction: column; - height: 92vh; - padding: 10px; + height: 100%; box-sizing: border-box; + overflow: hidden; } .header { diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index 1326e2a..b763e41 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -6,3 +6,13 @@ declare module '*.vue' { const component: DefineComponent export default component } +export {} +declare global { + interface Window { + electronWindow?: { + minimize: () => void + maximize: () => void + close: () => void + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 6668d7c..3edf44b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }], - "files": [] + "files": [], + "types": ["vite/client"] }