refactor(renderer): 重构项目前端

- 拆分了 CaptionData 和 ControlPage 组件
- 对部分页面和变量进行了重命名
- 重构优化了状态管理,新增状态管理
This commit is contained in:
himeditator
2025-07-02 20:56:21 +08:00
parent e77779b72a
commit 3dcba07b6e
14 changed files with 250 additions and 171 deletions

View File

@@ -23,5 +23,7 @@
## v1.0.0
2025-07-
> 预计为稳定版,之后除非大改,否则版本号第一位不再改变。

View File

@@ -0,0 +1,11 @@
# api-doc
本文档主要记录主进程和渲染进程的通信约定。
## 背景知识
本项目渲染进程包含两个:字幕窗口和控制窗口。主进程需要分别和两者进行通信,通信命令一般有三个关键词组成,由点号隔开。
第一个词表示发送/接收处理对象,`config` 表示配置对象,`engine` 表示字幕引擎对象,`both` 表示两者同时。
比如 ``

View File

@@ -1,34 +1,4 @@
<template>
<div class="caption-stat">
<a-row>
<a-col :span="6">
<a-statistic title="字幕引擎" :value="(customized && customizedApp)?'自定义':engine" />
</a-col>
<a-col :span="6">
<a-statistic title="字幕引擎状态" :value="engineEnabled?'已启动':'未启动'" />
</a-col>
<a-col :span="6">
<a-statistic title="已记录字幕" :value="captionData.length" />
</a-col>
</a-row>
</div>
<div class="caption-control">
<a-button
type="primary"
class="control-button"
@click="openCaptionWindow"
>打开字幕窗口</a-button>
<a-button
class="control-button"
@click="captionControl.startEngine"
>启动字幕引擎</a-button>
<a-button
danger class="control-button"
@click="captionControl.stopEngine"
>关闭字幕引擎</a-button>
</div>
<div class="caption-list">
<div class="caption-title">
<span style="margin-right: 30px;">字幕记录</span>
@@ -77,11 +47,8 @@
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useCaptionLogStore } from '@renderer/stores/captionLog'
import { useCaptionControlStore } from '@renderer/stores/captionControl'
const captionLog = useCaptionLogStore()
const { captionData } = storeToRefs(captionLog)
const captionControl = useCaptionControlStore()
const { engineEnabled, engine, customized, customizedApp } = storeToRefs(captionControl)
const pagination = ref({
current: 1,
pageSize: 10,
@@ -118,10 +85,6 @@ const columns = [
},
]
function openCaptionWindow() {
window.electron.ipcRenderer.send('control.captionWindow.activate')
}
function exportCaptions() {
const jsonData = JSON.stringify(captionData.value, null, 2)
const blob = new Blob([jsonData], { type: 'application/json' })
@@ -142,19 +105,6 @@ function clearCaptions() {
</script>
<style scoped>
.caption-control {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 30px;
}
.control-button {
height: 40px;
margin: 20px;
font-size: 16px;
}
.caption-list {
background: #fff;
padding: 20px;
@@ -199,4 +149,4 @@ function clearCaptions() {
padding-left: 16px;
border-left: 3px solid #1890ff;
}
</style>
</style>

View File

@@ -69,15 +69,15 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useCaptionControlStore } from '@renderer/stores/captionControl'
import { useEngineControlStore } from '@renderer/stores/engineControl'
import { notification } from 'ant-design-vue'
const captionControl = useCaptionControlStore()
const { captionEngine, audioType, changeSignal } = storeToRefs(captionControl)
const engineControl = useEngineControlStore()
const { captionEngine, audioType, changeSignal } = storeToRefs(engineControl)
const currentSourceLang = ref('auto')
const currentTargetLang = ref('zh')
const currentEngine = ref('gummy')
const currentEngine = ref<'gummy'>('gummy')
const currentAudio = ref<0 | 1>(0)
const currentTranslation = ref<boolean>(false)
@@ -95,17 +95,17 @@ const langList = computed(() => {
})
function applyChange(){
captionControl.sourceLang = currentSourceLang.value
captionControl.targetLang = currentTargetLang.value
captionControl.engine = currentEngine.value
captionControl.audio = currentAudio.value
captionControl.translation = currentTranslation.value
engineControl.sourceLang = currentSourceLang.value
engineControl.targetLang = currentTargetLang.value
engineControl.engine = currentEngine.value
engineControl.audio = currentAudio.value
engineControl.translation = currentTranslation.value
captionControl.customized = currentCustomized.value
captionControl.customizedApp = currentCustomizedApp.value
captionControl.customizedCommand = currentCustomizedCommand.value
engineControl.customized = currentCustomized.value
engineControl.customizedApp = currentCustomizedApp.value
engineControl.customizedCommand = currentCustomizedCommand.value
captionControl.sendControlChange()
engineControl.sendControlChange()
notification.open({
message: '字幕控制已更改',
@@ -114,21 +114,21 @@ function applyChange(){
}
function cancelChange(){
currentSourceLang.value = captionControl.sourceLang
currentTargetLang.value = captionControl.targetLang
currentEngine.value = captionControl.engine
currentAudio.value = captionControl.audio
currentTranslation.value = captionControl.translation
currentSourceLang.value = engineControl.sourceLang
currentTargetLang.value = engineControl.targetLang
currentEngine.value = engineControl.engine
currentAudio.value = engineControl.audio
currentTranslation.value = engineControl.translation
currentCustomized.value = captionControl.customized
currentCustomizedApp.value = captionControl.customizedApp
currentCustomizedCommand.value = captionControl.customizedCommand
currentCustomized.value = engineControl.customized
currentCustomizedApp.value = engineControl.customizedApp
currentCustomizedCommand.value = engineControl.customizedCommand
}
watch(changeSignal, (val) => {
if(val == true) {
cancelChange();
captionControl.changeSignal = false;
engineControl.changeSignal = false;
}
})
</script>
@@ -162,4 +162,4 @@ watch(changeSignal, (val) => {
font-size: 12px;
color: #666
}
</style>
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div class="caption-stat">
<a-row>
<a-col :span="6">
<a-statistic title="字幕引擎" :value="(customized && customizedApp)?'自定义':engine" />
</a-col>
<a-col :span="6">
<a-statistic title="字幕引擎状态" :value="engineEnabled?'已启动':'未启动'" />
</a-col>
<a-col :span="6">
<a-statistic title="已记录字幕" :value="captionData.length" />
</a-col>
</a-row>
</div>
<div class="caption-control">
<a-button
type="primary"
class="control-button"
@click="openCaptionWindow"
>打开字幕窗口</a-button>
<a-button
class="control-button"
@click="startEngine"
>启动字幕引擎</a-button>
<a-button
danger class="control-button"
@click="stopEngine"
>关闭字幕引擎</a-button>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCaptionLogStore } from '@renderer/stores/captionLog'
import { useEngineControlStore } from '@renderer/stores/engineControl'
const captionLog = useCaptionLogStore()
const { captionData } = storeToRefs(captionLog)
const engineControl = useEngineControlStore()
const { engineEnabled, engine, customized, customizedApp } = storeToRefs(engineControl)
function openCaptionWindow() {
window.electron.ipcRenderer.send('control.captionWindow.activate')
}
function startEngine() {
window.electron.ipcRenderer.send('control.engine.start')
}
function stopEngine() {
window.electron.ipcRenderer.send('control.engine.stop')
}
</script>
<style scoped>
.caption-control {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 30px;
}
.control-button {
height: 40px;
margin: 20px;
font-size: 16px;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<a-card size="small" title="页面宽度">
<template #extra>
<a-button type="link" @click="showAbout = true">关于本项目</a-button>
</template>
<div>
<a-input type="range" class="span-input" min="6" max="18" v-model:value="leftBarWidth" />
</div>
</a-card>
<a-modal v-model:open="showAbout" title="关于本项目" :footer="null">
<div class="about-modal-content">
<h2 class="about-title">Auto Caption 项目</h2>
<p class="about-desc">一个跨平台的实时字幕显示软件</p>
<a-divider />
<div class="about-info">
<p><b>作者</b>HiMeditator</p>
<p><b>版本</b>v0.1.0</p>
<p>
<b>项目地址</b>
<a href="https://github.com/HiMeditator/auto-caption" target="_blank">
GitHub | auto-caption
</a>
</p>
<p>
<b>用户手册</b>
<a
href="https://github.com/HiMeditator/auto-caption/blob/main/assets/user-manual_zh.md"
target="_blank"
>
GitHub | user-manual_zh.md
</a>
</p>
</div>
<div class="about-date">2026 6 26 </div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
const generalSettingStore = useGeneralSettingStore()
const { leftBarWidth } = storeToRefs(generalSettingStore)
const showAbout = ref(false)
</script>
<style scoped>
.span-input {
width: 100px;
}
</style>

View File

@@ -1,6 +1,6 @@
import './assets/reset.css'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
@@ -11,4 +11,5 @@ const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Antd)
app.mount('#app')
app.mount('#app')

View File

@@ -1,13 +1,6 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
interface CaptionItem {
index: number,
time_s: string,
time_t: string,
text: string,
translation: string
}
import { CaptionItem } from '../types'
export const useCaptionLogStore = defineStore('captionLog', () => {
const captionData = ref<CaptionItem[]>([])
@@ -34,4 +27,4 @@ export const useCaptionLogStore = defineStore('captionLog', () => {
captionData,
clear
}
})
})

View File

@@ -1,5 +1,6 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { Styles } from '@renderer/types'
export const useCaptionStyleStore = defineStore('captionStyle', () => {
const fontFamily = ref<string>('sans-serif')
@@ -7,7 +8,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
const fontColor = ref<string>('#000000')
const background = ref<string>('#dbe2ef')
const opacity = ref<number>(80)
const transDisplay = ref<boolean>(true)
const transFontFamily = ref<string>('sans-serif')
const transFontSize = ref<number>(24)
@@ -26,7 +27,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
})
function sendStyleChange() {
const styles = {
const styles: Styles = {
fontFamily: fontFamily.value,
fontSize: fontSize.value,
fontColor: fontColor.value,
@@ -69,7 +70,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
transFontColor, // 翻译字体颜色
backgroundRGBA, // 带透明度的背景颜色
sendStyleChange, // 发送样式改变
sendStyleReset, // 恢复默认样式
sendStyleReset, // 恢复默认样式
changeSignal // 样式改变信号
}
})
})

View File

@@ -5,7 +5,9 @@ import { notification } from 'ant-design-vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { h } from 'vue'
export const useCaptionControlStore = defineStore('captionControl', () => {
import { Controls } from '@renderer/types'
export const useEngineControlStore = defineStore('engineControl', () => {
const captionEngine = ref([
{
value: 'gummy',
@@ -39,7 +41,7 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
const sourceLang = ref<string>('en')
const targetLang = ref<string>('zh')
const engine = ref<string>('gummy')
const engine = ref<'gummy'>('gummy')
const audio = ref<0 | 1>(0)
const translation = ref<boolean>(true)
const customized = ref<boolean>(false)
@@ -49,7 +51,7 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
const changeSignal = ref<boolean>(false)
function sendControlChange() {
const controls = {
const controls: Controls = {
engineEnabled: engineEnabled.value,
sourceLang: sourceLang.value,
targetLang: targetLang.value,
@@ -63,14 +65,6 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
window.electron.ipcRenderer.send('control.control.change', controls)
}
function startEngine() {
window.electron.ipcRenderer.send('control.engine.start')
}
function stopEngine() {
window.electron.ipcRenderer.send('control.engine.stop')
}
window.electron.ipcRenderer.on('control.control.set', (_, controls) => {
sourceLang.value = controls.sourceLang
targetLang.value = controls.targetLang
@@ -84,16 +78,16 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
changeSignal.value = true
})
window.electron.ipcRenderer.on('control.engine.already', () => {
window.electron.ipcRenderer.on('control.engine.already', () => {
notification.open({
message: '字幕引擎已经启动',
description: '字幕引擎已经启动,请勿重复启动'
});
})
window.electron.ipcRenderer.on('control.engine.started', () => {
const str0 =
`原语言:${sourceLang.value},是否翻译:${translation.value?'是':'否'}` +
window.electron.ipcRenderer.on('control.engine.started', () => {
const str0 =
`原语言:${sourceLang.value},是否翻译:${translation.value?'是':'否'}` +
`字幕引擎:${engine.value},音频类型:${audio.value ? '输入音频' : '输出音频'}` +
(translation.value ? `,翻译语言:${targetLang.value}` : '');
const str1 = `类型:自定义引擎,引擎路径:${customizedApp.value},命令参数:${customizedCommand.value}`;
@@ -103,14 +97,14 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
});
})
window.electron.ipcRenderer.on('control.engine.stopped', () => {
window.electron.ipcRenderer.on('control.engine.stopped', () => {
notification.open({
message: '字幕引擎停止',
description: '可点击“启动字幕引擎”按钮重新启动'
});
})
window.electron.ipcRenderer.on('control.error.send', (_, message) => {
window.electron.ipcRenderer.on('control.error.send', (_, message) => {
notification.open({
message: '发生错误',
description: message,
@@ -133,8 +127,6 @@ export const useCaptionControlStore = defineStore('captionControl', () => {
customizedApp, // 自定义字幕引擎的应用程序
customizedCommand, // 自定义字幕引擎的命令
sendControlChange, // 发送最新控制消息到后端
startEngine, // 启动字幕引擎
stopEngine, // 停止字幕引擎
changeSignal, // 配置改变信号
}
})
})

View File

@@ -0,0 +1,9 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useGeneralSettingStore = defineStore('generalSetting', () => {
const leftBarWidth = ref<number>(8)
return {
leftBarWidth
}
})

View File

@@ -0,0 +1,33 @@
export type UILanguage = "zh" | "en" | "ja"
export interface Styles {
fontFamily: string,
fontSize: number,
fontColor: string,
background: string,
opacity: number,
transDisplay: boolean,
transFontFamily: string,
transFontSize: number,
transFontColor: string
}
export interface CaptionItem {
index: number,
time_s: string,
time_t: string,
text: string,
translation: string
}
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
audio: 0 | 1,
translation: boolean,
customized: boolean,
customizedApp: string,
customizedCommand: string
}

View File

@@ -111,7 +111,7 @@ function closeCaptionWindow() {
background-color: #2221;
}
.caption-container {
.caption-container {
-webkit-app-region: drag;
}
@@ -121,4 +121,4 @@ function closeCaptionWindow() {
line-height: 1.5em;
padding: 0 10px 10px 10px;
}
</style>
</style>

View File

@@ -1,63 +1,32 @@
<template>
<div>
<a-row>
<a-col :span="controlSpan">
<div class="caption-control">
<a-card size="small" title="页面宽度">
<template #extra>
<a-button type="link" @click="showAbout = true">关于本项目</a-button>
</template>
<div>
<a-input type="range" class="span-input" min="6" max="18" v-model:value="controlSpan" />
</div>
</a-card>
<CaptionControl />
<CaptionStyle />
</div>
</a-col>
<a-col :span="24 - controlSpan">
<div class="caption-data">
<CaptionData />
</div>
</a-col>
</a-row>
<a-modal v-model:open="showAbout" title="关于本项目" :footer="null">
<div class="about-modal-content">
<h2 class="about-title">Auto Caption 项目</h2>
<p class="about-desc">一个跨平台的实时字幕显示软件</p>
<a-divider />
<div class="about-info">
<p><b>作者</b>HiMeditator</p>
<p><b>版本</b>v0.1.0</p>
<p>
<b>项目地址</b>
<a href="https://github.com/HiMeditator/auto-caption" target="_blank">
GitHub | auto-caption
</a>
</p>
<p>
<b>用户手册</b>
<a
href="https://github.com/HiMeditator/auto-caption/blob/main/assets/user-manual_zh.md"
target="_blank"
>
GitHub | user-manual_zh.md
</a>
</p>
</div>
<div class="about-date">2026 6 26 </div>
<a-row>
<a-col :span="leftBarWidth">
<div class="caption-control">
<GeneralSetting />
<EngineControl />
<CaptionStyle />
</div>
</a-modal>
</div>
</a-col>
<a-col :span="24 - leftBarWidth">
<div class="caption-data">
<EngineStatus />
<CaptionLog />
</div>
</a-col>
</a-row>
</template>
<script setup lang="ts">
import GeneralSetting from '../components/GeneralSetting.vue'
import CaptionStyle from '../components/CaptionStyle.vue'
import CaptionControl from '../components/CaptionControl.vue';
import CaptionData from '../components/CaptionData.vue'
import { ref } from 'vue'
const controlSpan = ref(8)
const showAbout = ref(false)
import EngineControl from '../components/EngineControl.vue'
import EngineStatus from '@renderer/components/EngineStatus.vue'
import CaptionLog from '../components/CaptionLog.vue'
import { storeToRefs } from 'pinia'
import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
const generalSettingStore = useGeneralSettingStore()
const { leftBarWidth } = storeToRefs(generalSettingStore)
</script>
<style scoped>
@@ -76,10 +45,6 @@ const showAbout = ref(false)
scrollbar-width: thin;
}
.span-input {
width: 100px;
}
.about-modal-content {
text-align: center;
padding: 8px 0 0 0;
@@ -109,4 +74,4 @@ const showAbout = ref(false)
font-size: 0.95em;
text-align: right;
}
</style>
</style>