feat(theme): 添加暗色主题支持

- 新增暗色主题选项和系统主题自动适配功能
- 调整了部分样式以适应暗色主题
This commit is contained in:
himeditator
2025-07-05 00:54:12 +08:00
parent 14e7a7bce4
commit f29e15cde5
20 changed files with 192 additions and 46 deletions

View File

@@ -35,6 +35,7 @@
- 优化界面布局
- 添加更多可保存和载入的配置项
- 为字幕引擎添加更严格的状态限制,防止出现僵尸进程
### 新增文档

View File

@@ -1,4 +1,5 @@
- [x] 添加英语和日语语言支持
- [ ] 优化长字幕显示效果
- [x] 添加暗色主题
- [ ] 修复字幕引擎空置报错的问题
- [ ] 添加更多字幕引擎

View File

@@ -31,6 +31,19 @@
- 发送:无数据
- 接收:`FullConfig`
### `control.nativeTheme.get`
**介绍:**前端获取系统当前的主题
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**
- 发送:无数据
- 接收:`string`
## 前端 ==> 后端
### `control.uiLanguage.change`
@@ -43,6 +56,16 @@
**数据类型:**`UILanguage`
### `control.uiTheme.change`
**介绍:**前端修改字界面主题,将修改同步给后端
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**`UITheme`
### `control.leftBarWidth.change`
**介绍:**前端修改边栏宽度,将修改同步给后端
@@ -165,7 +188,7 @@
## 后端 ==> 前端
### `caption.uiLanguage.set`
### `control.uiLanguage.set`
**介绍:**后端将最新界面语言发送给前端,前端进行设置
@@ -175,6 +198,16 @@
**数据类型:**`UILanguage`
### `control.nativeTheme.change`
**介绍:**系统主题发生改变
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**`string`
### `control.engine.started`
**介绍:**引擎启动成功

View File

@@ -1,4 +1,4 @@
import { shell, BrowserWindow, ipcMain } from 'electron'
import { shell, BrowserWindow, ipcMain, nativeTheme } from 'electron'
import path from 'path'
import { is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
@@ -50,17 +50,37 @@ class ControlWindow {
}
public handleMessage() {
nativeTheme.on('updated', () => {
if(allConfig.uiTheme === 'system'){
if(nativeTheme.shouldUseDarkColors && this.window){
this.window.webContents.send('control.nativeTheme.change', 'dark')
}
else if(!nativeTheme.shouldUseDarkColors && this.window){
this.window.webContents.send('control.nativeTheme.change', 'light')
}
}
})
ipcMain.handle('both.window.mounted', () => {
return allConfig.getFullConfig()
})
ipcMain.handle('control.nativeTheme.get', () => {
if(nativeTheme.shouldUseDarkColors) return 'dark'
return 'light'
})
ipcMain.on('control.uiLanguage.change', (_, args) => {
allConfig.uiLanguage = args
if(captionWindow.window){
captionWindow.window.webContents.send('caption.uiLanguage.set', args)
captionWindow.window.webContents.send('control.uiLanguage.set', args)
}
})
ipcMain.on('control.uiTheme.change', (_, args) => {
allConfig.uiTheme = args
})
ipcMain.on('control.leftBarWidth.change', (_, args) => {
allConfig.leftBarWidth = args
})

View File

@@ -1,5 +1,7 @@
export type UILanguage = "zh" | "en" | "ja"
export type UITheme = "light" | "dark" | "system"
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
@@ -35,6 +37,7 @@ export interface CaptionItem {
export interface FullConfig {
uiLanguage: UILanguage,
uiTheme: UITheme,
leftBarWidth: number,
styles: Styles,
controls: Controls,

View File

@@ -1,6 +1,6 @@
import {
UILanguage, Styles, CaptionItem, Controls,
FullConfig
UILanguage, UITheme, Styles, Controls,
CaptionItem, FullConfig
} from '../types'
import { app, BrowserWindow } from 'electron'
import * as path from 'path'
@@ -35,6 +35,7 @@ const defaultControls: Controls = {
class AllConfig {
uiLanguage: UILanguage = 'zh';
leftBarWidth: number = 8;
uiTheme: UITheme = 'system';
styles: Styles = {...defaultStyles};
controls: Controls = {...defaultControls};
captionLog: CaptionItem[] = [];
@@ -46,6 +47,7 @@ class AllConfig {
if(fs.existsSync(configPath)){
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
if(config.uiLanguage) this.uiLanguage = config.uiLanguage
if(config.uiTheme) this.uiTheme = config.uiTheme
if(config.leftBarWidth) this.leftBarWidth = config.leftBarWidth
if(config.styles) this.setStyles(config.styles)
if(config.controls) this.setControls(config.controls)
@@ -56,6 +58,7 @@ class AllConfig {
public writeConfig() {
const config = {
uiLanguage: this.uiLanguage,
uiTheme: this.uiTheme,
leftBarWidth: this.leftBarWidth,
controls: this.controls,
styles: this.styles
@@ -68,6 +71,7 @@ class AllConfig {
public getFullConfig(): FullConfig {
return {
uiLanguage: this.uiLanguage,
uiTheme: this.uiTheme,
leftBarWidth: this.leftBarWidth,
styles: this.styles,
controls: this.controls,

View File

@@ -4,16 +4,20 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { FullConfig } from './types'
import { useCaptionLogStore } from './stores/captionLog'
import { useCaptionStyleStore } from './stores/captionStyle'
import { useEngineControlStore } from './stores/engineControl'
import { useGeneralSettingStore } from './stores/generalSetting'
const router = useRouter()
onMounted(() => {
console.log('Current route:', router.currentRoute.value.fullPath)
window.electron.ipcRenderer.invoke('both.window.mounted').then((data: FullConfig) => {
console.log(data)
useGeneralSettingStore().uiLanguage = data.uiLanguage
useGeneralSettingStore().uiTheme = data.uiTheme
useGeneralSettingStore().leftBarWidth = data.leftBarWidth
useCaptionStyleStore().setStyles(data.styles)
useEngineControlStore().setControls(data.controls)

View File

@@ -1,3 +1,10 @@
/* :root {
:root {
--control-background: #fff;
}
} */
body {
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
}

View File

@@ -1,6 +0,0 @@
body {
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
}

View File

@@ -1,7 +1,9 @@
<template>
<div class="caption-list">
<div class="caption-title">
<span style="margin-right: 30px;">{{ $t('log.title') }}</span>
<div>
<a-app class="caption-title">
<span style="margin-right: 30px;">{{ $t('log.title') }}</span>
</a-app>
<a-button
type="primary"
style="margin-right: 20px;"
@@ -106,13 +108,13 @@ function clearCaptions() {
<style scoped>
.caption-list {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.caption-title {
display: inline-block;
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
@@ -139,13 +141,11 @@ function clearCaptions() {
.caption-text {
font-size: 16px;
color: #333;
margin-bottom: 4px;
}
.caption-translation {
font-size: 14px;
color: #666;
padding-left: 16px;
border-left: 3px solid #1890ff;
}

View File

@@ -19,6 +19,15 @@
</a-radio-group>
</div>
<div class="input-item">
<span class="input-label">{{ $t('general.theme') }}</span>
<a-radio-group v-model:value="uiTheme">
<a-radio-button value="system">{{ $t('general.system') }}</a-radio-button>
<a-radio-button value="light">{{ $t('general.light') }}</a-radio-button>
<a-radio-button value="dark">{{ $t('general.dark') }}</a-radio-button>
</a-radio-group>
</div>
<div class="input-item">
<span class="input-label">{{ $t('general.barWidth') }}</span>
<a-input
@@ -37,7 +46,7 @@ import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
import { InfoCircleOutlined } from '@ant-design/icons-vue';
const generalSettingStore = useGeneralSettingStore()
const { uiLanguage, leftBarWidth } = storeToRefs(generalSettingStore)
const { uiLanguage, uiTheme, leftBarWidth } = storeToRefs(generalSettingStore)
</script>
<style scoped>

View File

@@ -0,0 +1,10 @@
import { theme } from 'ant-design-vue';
export const antDesignTheme = {
light: {
token: {}
},
dark: {
algorithm: theme.darkAlgorithm,
}
}

View File

@@ -16,3 +16,4 @@ export const i18n = createI18n({
export * from './config/engine'
export * from './config/audio'
export * from './config/theme'

View File

@@ -26,7 +26,11 @@ export default {
"title": "General Settings",
"uiLanguage": "Language",
"barWidth": "Width",
"note": "General Settings take effect immediately. Please note that changes to the Caption Engine Settings and Caption Style Settings will only take effect after clicking Apply."
"note": "General Settings take effect immediately. Please note that changes to the Caption Engine Settings and Caption Style Settings will only take effect after clicking Apply.",
"theme": "theme",
"light": "light",
"dark": "dark",
"system": "system"
},
engine: {
"title": "Caption Engine Settings",

View File

@@ -26,7 +26,11 @@ export default {
"title": "一般設定",
"uiLanguage": "言語設定",
"barWidth": "左側の幅",
"note": "一般設定はすぐに有効になります。字幕エンジンの設定と字幕スタイルの設定を変更した場合は、適用ボタンをクリックしてから有効になりますのでご注意ください。"
"note": "一般設定はすぐに有効になります。字幕エンジンの設定と字幕スタイルの設定を変更した場合は、適用ボタンをクリックしてから有効になりますのでご注意ください。",
"theme": "テーマ",
"light": "明るい",
"dark": "暗い",
"system": "システム"
},
engine: {
"title": "字幕エンジン設定",

View File

@@ -28,7 +28,11 @@ export default {
"title": "通用设置",
"uiLanguage": "界面语言",
"barWidth": "左侧宽度",
"note": "通用设置修改后立即生效。注意字幕引擎设置和字幕样式的设置修改后需要点击应用后才会生效。"
"note": "通用设置修改后立即生效。注意字幕引擎设置和字幕样式的设置修改后需要点击应用后才会生效。",
"theme": "主题",
"light": "浅色",
"dark": "深色",
"system": "系统"
},
engine: {
"title": "字幕引擎设置",

View File

@@ -1,4 +1,4 @@
import './assets/reset.css'
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'

View File

@@ -1,15 +1,18 @@
import { ref, watch } from 'vue'
import { defineStore } from 'pinia'
import { i18n } from '../i18n'
import type { UILanguage } from '../types'
import type { UILanguage, UITheme } from '../types'
import { engines, audioTypes } from '../i18n'
import { engines, audioTypes, antDesignTheme } from '../i18n'
import { useEngineControlStore } from './engineControl'
export const useGeneralSettingStore = defineStore('generalSetting', () => {
const uiLanguage = ref<UILanguage>('zh')
const uiTheme = ref<UITheme>('system')
const leftBarWidth = ref<number>(8)
const antdTheme = ref<Object>(antDesignTheme['light'])
watch(uiLanguage, (newValue) => {
i18n.global.locale.value = newValue
useEngineControlStore().captionEngine = engines[newValue]
@@ -17,16 +20,47 @@ export const useGeneralSettingStore = defineStore('generalSetting', () => {
window.electron.ipcRenderer.send('control.uiLanguage.change', newValue)
})
watch(uiTheme, (newValue) => {
window.electron.ipcRenderer.send('control.uiTheme.change', newValue)
if(newValue === 'system'){
window.electron.ipcRenderer.invoke('control.nativeTheme.get').then((theme) => {
if(theme === 'light') setLightTheme()
else if(theme === 'dark') setDarkTheme()
})
}
else if(newValue === 'light') setLightTheme()
else if(newValue === 'dark') setDarkTheme()
})
watch(leftBarWidth, (newValue) => {
window.electron.ipcRenderer.send('control.leftBarWidth.change', newValue)
})
window.electron.ipcRenderer.on('caption.uiLanguage.set', (_, args: UILanguage) => {
window.electron.ipcRenderer.on('control.uiLanguage.set', (_, args: UILanguage) => {
uiLanguage.value = args
})
window.electron.ipcRenderer.on('control.nativeTheme.change', (_, args) => {
if(args === 'light') setLightTheme()
else if(args === 'dark') setDarkTheme()
})
function setLightTheme(){
antdTheme.value = antDesignTheme.light
const root = document.documentElement
root.style.setProperty('--control-background', '#fff')
}
function setDarkTheme(){
antdTheme.value = antDesignTheme.dark
const root = document.documentElement
root.style.setProperty('--control-background', '#000')
}
return {
uiLanguage,
leftBarWidth
uiTheme,
leftBarWidth,
antdTheme
}
})

View File

@@ -1,5 +1,7 @@
export type UILanguage = "zh" | "en" | "ja"
export type UITheme = "light" | "dark" | "system"
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
@@ -35,6 +37,7 @@ export interface CaptionItem {
export interface FullConfig {
uiLanguage: UILanguage,
uiTheme: UITheme,
leftBarWidth: number,
styles: Styles,
controls: Controls,

View File

@@ -1,19 +1,21 @@
<template>
<a-row>
<a-col :span="leftBarWidth">
<div class="caption-control">
<GeneralSetting />
<EngineControl />
<CaptionStyle />
</div>
</a-col>
<a-col :span="24 - leftBarWidth">
<div class="caption-data">
<EngineStatus />
<CaptionLog />
</div>
</a-col>
</a-row>
<a-config-provider :theme="antdTheme">
<a-row class="control-container">
<a-col :span="leftBarWidth">
<div class="caption-control">
<GeneralSetting />
<EngineControl />
<CaptionStyle />
</div>
</a-col>
<a-col :span="24 - leftBarWidth">
<div class="caption-data">
<EngineStatus />
<CaptionLog />
</div>
</a-col>
</a-row>
</a-config-provider>
</template>
<script setup lang="ts">
@@ -26,22 +28,30 @@ import { storeToRefs } from 'pinia'
import { useGeneralSettingStore } from '@renderer/stores/generalSetting'
const generalSettingStore = useGeneralSettingStore()
const { leftBarWidth } = storeToRefs(generalSettingStore)
const { leftBarWidth, antdTheme } = storeToRefs(generalSettingStore)
</script>
<style scoped>
.control-container {
background-color: var(--control-background);
}
.caption-control {
height: 100vh;
border-right: 1px solid #7774;
padding: 20px;
overflow-y: auto;
scrollbar-width: thin;
}
.caption-data {
height: 100vh;
padding: 20px;
overflow-y: auto;
scrollbar-width: thin;
}
.caption-control::-webkit-scrollbar,
.caption-data::-webkit-scrollbar {
display: none;
}
</style>