Skip to content

Commit 1205bd6

Browse files
committed
feat: 实现跨平台无边框窗口与自定义标题栏
- 添加 Wails 配置文件的 JSON Schema 支持 - 启用无边框窗口并设置透明背景,支持 macOS 和 Windows 平台差异化样式 - 重构评论服务中的 Switch 类型为 SwitchConfig 以保持命名一致性 - 前端根据运行平台动态添加 CSS 类,实现圆角窗口等平台特有样式 - 在菜单组件中添加自定义窗口控制按钮(最小化/最大化/关闭),支持双击标题栏最大化 - 更新 TypeScript 模型定义以匹配后端类型变更
1 parent a68dc48 commit 1205bd6

7 files changed

Lines changed: 208 additions & 13 deletions

File tree

backend/services/comment.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ type Comment struct {
8181
SourceType int `json:"source_type"`
8282
State int `json:"state"`
8383
StyleNoteLine string `json:"style_note_line"`
84-
Switch Switch `json:"switch"`
84+
SwitchConfig SwitchConfig `json:"switch"`
8585
Tags []interface{} `json:"tags"`
8686
Uid int `json:"uid"`
8787
UidHazy string `json:"uid_hazy"`
@@ -100,7 +100,7 @@ type Lesson struct {
100100
Ptype int `json:"ptype"`
101101
PidHazy string `json:"pid_hazy"`
102102
}
103-
type Switch struct {
103+
type SwitchConfig struct {
104104
ImgOrigin bool `json:"img_origin"`
105105
}
106106

frontend/src/App.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,33 @@ import { themeStore } from './stores/theme'
1010
import { settingStore } from './stores/setting'
1111
import { playerStore } from './stores/player'
1212
import { AudioDetailAlias, SetDir } from '../wailsjs/go/backend/App'
13+
import { Environment } from '../wailsjs/runtime'
1314
import { setFontFamily } from './utils/utils'
1415
1516
// 初始化主题
1617
const store = themeStore()
1718
const sStore = settingStore()
19+
const applyPlatformClass = async () => {
20+
const root = document.documentElement
21+
root.classList.remove('platform-mac', 'platform-windows', 'platform-linux')
22+
try {
23+
const env = await Environment()
24+
const platform = String(env?.platform || '').toLowerCase()
25+
if (platform === 'darwin') {
26+
root.classList.add('platform-mac')
27+
return
28+
}
29+
if (platform === 'windows') {
30+
root.classList.add('platform-windows')
31+
return
32+
}
33+
if (platform === 'linux') {
34+
root.classList.add('platform-linux')
35+
}
36+
} catch {}
37+
}
1838
onMounted(() => {
39+
applyPlatformClass()
1940
store.initTheme()
2041
setFontFamily(sStore.setting.fontFamily || 'default')
2142
// 启动时把持久化的路径同步到后端,让后台下载任务管理器(DownloadTaskPanel)

frontend/src/components/Menu.vue

Lines changed: 163 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
<template>
2-
<div class="menu-container">
2+
<div class="menu-container" style="--wails-draggable:drag" @dblclick="handleTitlebarDblClick">
3+
<!-- 窗口控制按钮 -->
4+
<div class="window-controls" :class="isWindows ? 'windows-style' : 'mac-style'">
5+
<template v-if="isWindows">
6+
<!-- Windows 风格 -->
7+
<button class="window-btn windows-btn" @click="windowMinimize" title="最小化">
8+
<svg viewBox="0 0 10 10" class="icon-minimize"><path d="M0 5h10" stroke="currentColor" stroke-width="1"/></svg>
9+
</button>
10+
<button class="window-btn windows-btn" @click="windowMaximize" :title="isMaximized ? '还原' : '最大化'">
11+
<svg v-if="!isMaximized" viewBox="0 0 10 10" class="icon-maximize"><rect x="0.5" y="0.5" width="9" height="9" stroke="currentColor" fill="none" stroke-width="1"/></svg>
12+
<svg v-else viewBox="0 0 10 10" class="icon-restore">
13+
<path d="M3 1.5H8.5V7H7.5V2.5H3V1.5Z" fill="currentColor"/>
14+
<path d="M1.5 3H7V8.5H1.5V3ZM2.5 4V7.5H6V4H2.5Z" fill="currentColor"/>
15+
</svg>
16+
</button>
17+
<button class="window-btn windows-btn close-btn windows-close" @click="windowClose" title="关闭">
18+
<svg viewBox="0 0 10 10" class="icon-close"><path d="M0 0L10 10M10 0L0 10" stroke="currentColor" stroke-width="1"/></svg>
19+
</button>
20+
</template>
21+
<template v-else>
22+
<!-- macOS 风格 -->
23+
<button class="window-btn mac-btn close-btn" @click="windowClose" title="关闭"></button>
24+
<button class="window-btn mac-btn minimize-btn" @click="windowMinimize" title="最小化"></button>
25+
<button class="window-btn mac-btn maximize-btn" @click="windowMaximize" title="最大化"></button>
26+
</template>
27+
</div>
28+
329
<el-menu router :default-active="activeIndex" class="el-menu" mode="horizontal"
430
text-color="var(--text-primary)"
531
active-text-color="var(--accent-color)"
@@ -25,11 +51,12 @@
2551
</template>
2652

2753
<script lang="ts" setup>
28-
import { ref, computed } from 'vue'
54+
import { ref, computed, onMounted } from 'vue'
2955
import { useRouter, useRoute } from 'vue-router'
3056
import { Moon, Sunny } from '@element-plus/icons-vue'
3157
import MenuItem from './MenuItem.vue'
3258
import { themeStore } from '../stores/theme'
59+
import { Environment, Quit, WindowIsMaximised, WindowMinimise, WindowToggleMaximise } from '../../wailsjs/runtime'
3360
3461
const route = useRoute()
3562
const router = useRouter()
@@ -44,6 +71,58 @@ const toggleDark = () => {
4471
console.log('切换后状态:', store.isDark)
4572
}
4673
74+
const isWindows = ref(false)
75+
const isMaximized = ref(false)
76+
77+
const initPlatform = async () => {
78+
try {
79+
const env = await Environment()
80+
isWindows.value = env.platform === 'windows'
81+
if (isWindows.value) {
82+
await syncMaximizedState()
83+
}
84+
} catch {
85+
isWindows.value = false
86+
}
87+
}
88+
89+
const syncMaximizedState = async () => {
90+
try {
91+
isMaximized.value = await WindowIsMaximised()
92+
} catch {
93+
isMaximized.value = false
94+
}
95+
}
96+
97+
onMounted(() => {
98+
initPlatform()
99+
})
100+
101+
const windowClose = () => {
102+
Quit()
103+
}
104+
105+
const windowMinimize = () => {
106+
WindowMinimise()
107+
}
108+
109+
const windowMaximize = async () => {
110+
WindowToggleMaximise()
111+
await syncMaximizedState()
112+
}
113+
114+
const handleTitlebarDblClick = async (event: MouseEvent) => {
115+
if (!isWindows.value) {
116+
return
117+
}
118+
const target = event.target as HTMLElement | null
119+
const ignore = target?.closest('.window-controls, .theme-switch-container, .el-menu')
120+
if (ignore) {
121+
return
122+
}
123+
await windowMaximize()
124+
}
125+
47126
const allRoutes = router.options.routes.filter(route =>
48127
route.meta?.name !== '主题' // 过滤掉主题页面
49128
)
@@ -192,4 +271,85 @@ const handleSelect = (key: string, keyPath: string[]) => {
192271
background-color: var(--card-hover-bg) !important;
193272
border-bottom-color: var(--accent-color) !important;
194273
}
195-
</style>
274+
275+
/* 窗口控制按钮通用样式 */
276+
.window-controls {
277+
display: flex;
278+
align-items: center;
279+
padding: 0 12px;
280+
margin-right: 16px;
281+
-webkit-app-region: no-drag;
282+
}
283+
284+
.window-controls.mac-style {
285+
gap: 8px;
286+
}
287+
288+
.window-controls.windows-style {
289+
gap: 4px;
290+
}
291+
292+
.window-btn {
293+
border: none;
294+
cursor: pointer;
295+
transition: opacity 0.2s ease;
296+
display: flex;
297+
align-items: center;
298+
justify-content: center;
299+
}
300+
301+
/* macOS 风格按钮 */
302+
.mac-btn {
303+
width: 12px;
304+
height: 12px;
305+
border-radius: 50%;
306+
}
307+
308+
.mac-btn:hover {
309+
opacity: 0.8;
310+
}
311+
312+
.mac-btn.close-btn {
313+
background-color: #ff5f57;
314+
}
315+
316+
.mac-btn.minimize-btn {
317+
background-color: #febc2e;
318+
}
319+
320+
.mac-btn.maximize-btn {
321+
background-color: #28c840;
322+
}
323+
324+
/* Windows 风格按钮 */
325+
.windows-btn {
326+
width: 28px;
327+
height: 28px;
328+
background: transparent;
329+
border-radius: 4px;
330+
}
331+
332+
.windows-btn:hover {
333+
background-color: rgba(255, 255, 255, 0.1);
334+
}
335+
336+
.windows-btn .icon-minimize,
337+
.windows-btn .icon-maximize,
338+
.windows-btn .icon-close {
339+
width: 10px;
340+
height: 10px;
341+
color: var(--text-primary);
342+
}
343+
344+
.windows-btn.windows-close:hover {
345+
background-color: #e81123;
346+
}
347+
348+
.windows-btn.windows-close .icon-close {
349+
color: var(--text-primary);
350+
}
351+
352+
.windows-btn.windows-close:hover .icon-close {
353+
color: white;
354+
}
355+
</style>

frontend/src/style.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
}
104104

105105
html {
106-
background-color: var(--bg-color);
106+
background-color: transparent;
107107
color: var(--text-color);
108108
text-align: center;
109109
width: 100%;
@@ -112,19 +112,31 @@ html {
112112
transition: background-color 0.3s ease, color 0.3s ease;
113113
}
114114

115+
:root {
116+
--window-radius: 0px;
117+
}
118+
119+
html.platform-mac {
120+
--window-radius: 10px;
121+
}
122+
115123
body {
116124
margin: 0;
117125
color: var(--text-color-secondary);
118126
font-family: var(--font-family-base);
119127
line-height: 1.5;
120128
padding: 0;
121-
background-color: var(--bg-color);
129+
background-color: transparent;
130+
border-radius: var(--window-radius);
131+
border: 1px solid transparent;
132+
overflow: hidden;
122133
transition: background-color 0.3s ease, color 0.3s ease;
123134
}
124135

125136
#app {
126137
height: 100vh;
127138
text-align: center;
139+
border-radius: var(--window-radius);
128140
overflow: hidden;
129141
}
130142

frontend/wailsjs/go/models.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2316,11 +2316,11 @@ export namespace services {
23162316
this.view_count = source["view_count"];
23172317
}
23182318
}
2319-
export class Switch {
2319+
export class SwitchConfig {
23202320
img_origin: boolean;
23212321

23222322
static createFrom(source: any = {}) {
2323-
return new Switch(source);
2323+
return new SwitchConfig(source);
23242324
}
23252325

23262326
constructor(source: any = {}) {
@@ -2577,7 +2577,7 @@ export namespace services {
25772577
source_type: number;
25782578
state: number;
25792579
style_note_line: string;
2580-
switch: Switch;
2580+
switch: SwitchConfig;
25812581
tags: any[];
25822582
uid: number;
25832583
uid_hazy: string;
@@ -2656,7 +2656,7 @@ export namespace services {
26562656
this.source_type = source["source_type"];
26572657
this.state = source["state"];
26582658
this.style_note_line = source["style_note_line"];
2659-
this.switch = this.convertValues(source["switch"], Switch);
2659+
this.switch = this.convertValues(source["switch"], SwitchConfig);
26602660
this.tags = source["tags"];
26612661
this.uid = source["uid"];
26622662
this.uid_hazy = source["uid_hazy"];

main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ func main() {
3434
MinHeight: 768,
3535
MaxWidth: 2560,
3636
MaxHeight: 1440,
37+
Frameless: true,
3738
AssetServer: &assetserver.Options{
3839
Assets: assets,
3940
},
40-
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
41+
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0},
4142
OnStartup: app.Startup,
4243
OnShutdown: app.Shutdown,
4344
OnDomReady: app.DomReady,
@@ -80,7 +81,7 @@ func main() {
8081
UseToolbar: false,
8182
HideToolbarSeparator: true,
8283
},
83-
Appearance: mac.DefaultAppearance,
84+
Appearance: mac.NSAppearanceNameVibrantLight,
8485
WebviewIsTransparent: true,
8586
WindowIsTranslucent: true,
8687
About: &mac.AboutInfo{

wails.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"$schema": "https://wails.io/schemas/config.v2.json",
23
"name": "dedao-gui",
34
"outputfilename": "dedao-gui",
45
"frontend:install": "npm install",

0 commit comments

Comments
 (0)