refactor(App): 移除窗口控制按钮并重构标题栏结构

- 移除 src-tauri/capabilities/default.json 中的 core🪟allow-toggle-maximize 权限
- 从 App.vue 中移除自定义标题栏实现,包括拖拽区域和窗口控制按钮
- 移除与窗口控制相关的函数(minimizeWindow、toggleMaximizeWindow、closeWindow 等)
- 移除标题栏相关样式和 CSS 类
- 新增独立的 WindowTitlebar 组件来处理窗口控制功能
- 在 MainLayout.vue 中集成新的 WindowTitlebar 组件
- 调整布局结构以支持新的标题栏组件
- 更新全局样式变量,添加 --playerbar-height 变量
- 移除 DiscoverView.vue 中未使用的分类头部元素
- 在 SettingsView.vue 中导入 computed 函数

此重构简化了主应用组件,并将窗口控制功能分离到专用组件中,
提高了代码的模块化程度和可维护性。
This commit is contained in:
Yuhang Wu 2026-08-14 17:11:13 +08:00
parent 5bed7cd4cb
commit cc2fdfa04f
8 changed files with 185 additions and 205 deletions

View File

@ -9,7 +9,6 @@
"core:window:allow-start-dragging", "core:window:allow-start-dragging",
"core:window:allow-close", "core:window:allow-close",
"core:window:allow-minimize", "core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"opener:default", "opener:default",
"store:default" "store:default"
] ]

View File

@ -1,36 +1,15 @@
<template> <template>
<div class="app-root"> <n-config-provider :theme-overrides="themeOverrides">
<div class="titlebar" :class="{ 'titlebar-macos': isMacos }"> <n-message-provider>
<div class="titlebar-drag" data-tauri-drag-region @mousedown="handleTitlebarMouseDown"> <n-notification-provider>
<span class="titlebar-dot"></span> <n-dialog-provider>
<span class="titlebar-title">YwYMusic</span> <div class="app-root">
</div> <RouterView />
</div>
<div class="titlebar-controls"> </n-dialog-provider>
<button type="button" aria-label="最小化" @click="minimizeWindow"> </n-notification-provider>
<span class="window-icon window-icon-minimize"></span> </n-message-provider>
</button> </n-config-provider>
<button type="button" aria-label="最大化或还原" @click="toggleMaximizeWindow">
<span class="window-icon window-icon-maximize"></span>
</button>
<button type="button" class="titlebar-close" aria-label="关闭" @click="closeWindow">
<span class="window-icon window-icon-close"></span>
</button>
</div>
</div>
<n-config-provider :theme-overrides="themeOverrides">
<n-message-provider>
<n-notification-provider>
<n-dialog-provider>
<div class="app-content">
<RouterView />
</div>
</n-dialog-provider>
</n-notification-provider>
</n-message-provider>
</n-config-provider>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -41,12 +20,8 @@ import {
NNotificationProvider, NNotificationProvider,
type GlobalThemeOverrides, type GlobalThemeOverrides,
} from "naive-ui"; } from "naive-ui";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { RouterView } from "vue-router"; import { RouterView } from "vue-router";
const appWindow = getCurrentWindow();
const isMacos = navigator.userAgent.includes("Mac OS X");
const themeOverrides: GlobalThemeOverrides = { const themeOverrides: GlobalThemeOverrides = {
common: { common: {
primaryColor: "#b4532a", primaryColor: "#b4532a",
@ -75,168 +50,12 @@ const themeOverrides: GlobalThemeOverrides = {
"'Inter','HarmonyOS Sans SC','PingFang SC','Microsoft YaHei',system-ui,-apple-system,sans-serif", "'Inter','HarmonyOS Sans SC','PingFang SC','Microsoft YaHei',system-ui,-apple-system,sans-serif",
}, },
}; };
function minimizeWindow() {
void appWindow.minimize();
}
function toggleMaximizeWindow() {
void appWindow.toggleMaximize();
}
function closeWindow() {
void appWindow.close();
}
function handleTitlebarMouseDown(event: MouseEvent) {
if (event.detail === 2) {
void appWindow.toggleMaximize();
}
}
</script> </script>
<style scoped> <style scoped>
.app-root { .app-root {
height: 100%; height: 100%;
padding-top: var(--titlebar-height); min-height: 0;
background: var(--app-bg); background: var(--app-bg);
} }
.titlebar {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 9999;
display: grid;
grid-template-columns: 1fr auto;
height: var(--titlebar-height);
overflow: hidden;
user-select: none;
background: rgba(234, 220, 200, 0.86);
border-bottom: 1px solid rgba(180, 83, 42, 0.12);
backdrop-filter: blur(16px);
}
.titlebar-drag {
display: flex;
align-items: center;
height: 100%;
gap: 8px;
padding-left: 14px;
}
.titlebar-macos {
background: rgba(244, 237, 223, 0.72);
}
.titlebar-macos .titlebar-drag {
padding-left: 78px;
}
.titlebar-macos .titlebar-controls,
.titlebar-macos .titlebar-dot {
display: none;
}
.titlebar-macos .titlebar-title {
color: var(--app-text-muted);
}
.titlebar-dot {
width: 9px;
height: 9px;
border-radius: 999px;
background: linear-gradient(135deg, #d98b5f 0%, var(--app-primary) 100%);
box-shadow: 0 0 0 3px rgba(180, 83, 42, 0.12);
}
.titlebar-title {
color: var(--app-text-secondary);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.2px;
}
.titlebar-controls {
display: flex;
height: 100%;
}
.titlebar-controls button {
display: grid;
place-items: center;
width: 46px;
height: 100%;
padding: 0;
color: var(--app-text-secondary);
background: transparent;
border: 0;
cursor: pointer;
transition:
color 0.15s ease,
background-color 0.15s ease;
}
.titlebar-controls button:hover {
color: var(--app-text);
background: rgba(180, 83, 42, 0.1);
}
.titlebar-controls .titlebar-close:hover {
color: #fffaf0;
background: #d83b32;
}
.window-icon {
position: relative;
display: block;
width: 12px;
height: 12px;
}
.window-icon-minimize::before,
.window-icon-maximize::before,
.window-icon-close::before,
.window-icon-close::after {
position: absolute;
content: "";
}
.window-icon-minimize::before {
right: 1px;
bottom: 3px;
left: 1px;
height: 1.5px;
background: currentColor;
}
.window-icon-maximize::before {
inset: 1px;
border: 1.5px solid currentColor;
border-radius: 1px;
}
.window-icon-close::before,
.window-icon-close::after {
top: 5px;
left: 0;
width: 12px;
height: 1.5px;
background: currentColor;
border-radius: 999px;
}
.window-icon-close::before {
transform: rotate(45deg);
}
.window-icon-close::after {
transform: rotate(-45deg);
}
.app-content {
height: calc(100vh - var(--titlebar-height));
min-height: 0;
}
</style> </style>

View File

@ -310,7 +310,7 @@ onUnmounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: clamp(12px, 2vw, 20px); gap: clamp(12px, 2vw, 20px);
height: clamp(72px, 9vh, 88px); height: 100%;
padding: 0 clamp(14px, 2vw, 22px); padding: 0 clamp(14px, 2vw, 22px);
flex-shrink: 0; flex-shrink: 0;
background: rgba(255, 250, 240, 0.94); background: rgba(255, 250, 240, 0.94);

View File

@ -0,0 +1,134 @@
<template>
<header class="window-titlebar" :class="{ 'window-titlebar--macos': isMacos }">
<div class="window-titlebar__drag" data-tauri-drag-region>
<!-- <span class="window-titlebar__dot"></span> -->
<span class="window-titlebar__title">YwYMusic</span>
</div>
<div class="window-titlebar__controls">
<n-button
class="window-titlebar__control"
quaternary
:bordered="false"
aria-label="最小化"
@click.stop="minimizeWindow"
>
<template #icon>
<n-icon :component="MinimizeOutlined" :size="18" />
</template>
</n-button>
<n-button
class="window-titlebar__control window-titlebar__control--close"
quaternary
:bordered="false"
aria-label="关闭"
@click.stop="closeWindow"
>
<template #icon>
<n-icon :component="CloseOutlined" :size="18" />
</template>
</n-button>
</div>
</header>
</template>
<script setup lang="ts">
import { getCurrentWindow } from "@tauri-apps/api/window"
import { NButton, NIcon } from "naive-ui"
import { CloseOutlined, MinimizeOutlined } from "@vicons/material"
const appWindow = getCurrentWindow()
const isMacos = navigator.userAgent.includes("Mac OS X")
function minimizeWindow() {
void appWindow.minimize()
}
function closeWindow() {
void appWindow.close()
}
</script>
<style scoped>
.window-titlebar {
position: relative;
z-index: 4;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
height: var(--titlebar-height);
overflow: hidden;
user-select: none;
background: rgba(234, 220, 200, 0.86);
backdrop-filter: blur(16px);
}
.window-titlebar__drag {
display: flex;
align-items: center;
min-width: 0;
height: 100%;
gap: 8px;
padding-left: 14px;
}
.window-titlebar__dot {
width: 9px;
height: 9px;
flex: 0 0 auto;
border-radius: 999px;
background: linear-gradient(135deg, #d98b5f 0%, var(--app-primary) 100%);
box-shadow: 0 0 0 3px rgba(180, 83, 42, 0.12);
}
.window-titlebar__title {
min-width: 0;
overflow: hidden;
color: var(--app-text-secondary);
font-size: 13px;
font-weight: 600;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
.window-titlebar__controls {
display: flex;
height: 100%;
}
.window-titlebar__control {
width: 46px;
height: var(--titlebar-height);
--n-border-radius: 0 !important;
--n-color-hover: rgba(180, 83, 42, 0.1) !important;
--n-color-pressed: rgba(180, 83, 42, 0.16) !important;
--n-text-color: var(--app-text-secondary) !important;
--n-text-color-hover: var(--app-text) !important;
--n-text-color-pressed: var(--app-text) !important;
}
.window-titlebar__control--close {
--n-color-hover: #d83b32 !important;
--n-color-pressed: #c43129 !important;
--n-text-color-hover: #fffaf0 !important;
--n-text-color-pressed: #fffaf0 !important;
}
.window-titlebar--macos {
background: rgba(244, 237, 223, 0.72);
}
.window-titlebar--macos .window-titlebar__drag {
padding-left: 78px;
}
.window-titlebar--macos .window-titlebar__controls,
.window-titlebar--macos .window-titlebar__dot {
display: none;
}
.window-titlebar--macos .window-titlebar__title {
color: var(--app-text-muted);
}
</style>

View File

@ -38,11 +38,15 @@
</div> </div>
</n-layout-sider> </n-layout-sider>
<n-layout-content ref="contentRef" class="content-layout" :native-scrollbar="false"> <div class="content-pane">
<main class="content"> <WindowTitlebar />
<RouterView />
</main> <n-layout-content ref="contentRef" class="content-layout" :native-scrollbar="false">
</n-layout-content> <main class="content">
<RouterView />
</main>
</n-layout-content>
</div>
</n-layout> </n-layout>
<n-layout-footer class="playerbar-footer"> <n-layout-footer class="playerbar-footer">
@ -61,6 +65,7 @@ import { NIcon } from "naive-ui"
import { MusicalNotesOutline, SearchOutline, SettingsOutline, TrophyOutline } from "@vicons/ionicons5" import { MusicalNotesOutline, SearchOutline, SettingsOutline, TrophyOutline } from "@vicons/ionicons5"
import { Playlist } from "@vicons/tabler" import { Playlist } from "@vicons/tabler"
import PlayerBar from "@/components/PlayerBar.vue" import PlayerBar from "@/components/PlayerBar.vue"
import WindowTitlebar from "@/components/WindowTitlebar.vue"
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -135,19 +140,35 @@ watch(
<style scoped> <style scoped>
.app-shell { .app-shell {
display: grid; display: grid;
grid-template-rows: minmax(0, 1fr) auto; grid-template-rows: minmax(0, 1fr) var(--playerbar-height);
height: 100%; width: 100%;
height: 100vh;
height: 100dvh;
max-height: 100vh;
max-height: 100dvh;
min-width: 0; min-width: 0;
min-height: 0;
overflow: hidden; overflow: hidden;
background: var(--app-bg); background: var(--app-bg);
} }
.workspace { .workspace {
width: 100%;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
} }
.content-pane {
display: grid;
flex: 1;
grid-template-rows: var(--titlebar-height) minmax(0, 1fr);
min-width: 0;
height: 100%;
min-height: 0;
background: var(--app-bg);
}
.sidebar-sider { .sidebar-sider {
min-height: 0; min-height: 0;
min-width: 0; min-width: 0;
@ -259,7 +280,9 @@ watch(
.content-layout { .content-layout {
min-width: 0; min-width: 0;
height: 100%;
min-height: 0; min-height: 0;
overflow: hidden;
} }
.content { .content {
@ -269,6 +292,9 @@ watch(
} }
.playerbar-footer { .playerbar-footer {
height: var(--playerbar-height);
min-height: 0;
overflow: hidden;
background: transparent; background: transparent;
} }
</style> </style>

View File

@ -1,5 +1,6 @@
:root { :root {
--titlebar-height: 34px; --titlebar-height: 34px;
--playerbar-height: clamp(72px, 9vh, 88px);
--app-bg: #f4eddf; --app-bg: #f4eddf;
--app-bg-elevated: #eee2d1; --app-bg-elevated: #eee2d1;
--app-card: #fffaf0; --app-card: #fffaf0;

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="page"> <div class="page">
<section class="section"> <section>
<div class="section-head"> <!-- <div class="section-head">
<h3>分类</h3> <h3>分类</h3>
<span class="section-sub">歌单分类</span> <span class="section-sub">歌单分类</span>
</div> </div> -->
<n-tabs v-model:value="activeCategory" type="line" animated @update:value="playList"> <n-tabs v-model:value="activeCategory" type="line" animated @update:value="playList">
<n-tab-pane v-for="category in categories" :key="category.id" :name="category.id" :tab="category.name" /> <n-tab-pane v-for="category in categories" :key="category.id" :name="category.id" :tab="category.name" />
</n-tabs> </n-tabs>

View File

@ -22,6 +22,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"
import { RouterView, useRoute, useRouter } from "vue-router" import { RouterView, useRoute, useRouter } from "vue-router"
import type { MenuOption } from "naive-ui" import type { MenuOption } from "naive-ui"