feat: implement custom window controls and replace header bar with title bar

- Add window handlers for minimize, maximize, close, and check if maximized in ipcMain.
- Update preload script to use new window control IPC events.
- Refactor window service to remove old IPC event handlers and use new handlers.
- Remove old HeaderBar and DragRegion components, replacing them with a new TitleBar component.
- Update Layout component to use TitleBar instead of HeaderBar.
- Remove useWinManager hook as its functionality is now integrated into TitleBar.
- Update login page to remove HeaderBar and adjust layout accordingly.
- Update constants to remove old window IPC events.
- Update package dependencies to replace @iconify/vue with @lucide/vue.
This commit is contained in:
duanshuwen
2026-04-14 23:38:42 +08:00
parent 6fd51d04dd
commit b5a67ff650
20 changed files with 642 additions and 340 deletions

View File

@@ -1,13 +0,0 @@
<template>
<div class="drag-region">
<slot>
<span class="_placeholder">_hidden</span>
</slot>
</div>
</template>
<style scoped>
._placeholder {
opacity: 0;
}
</style>

View File

@@ -1,76 +0,0 @@
<template>
<header class="flex items-start justify-between h-[40px]">
<div class="title-bar-main flex-auto">
<slot>{{ title ?? '' }}</slot>
</div>
<div class="title-bar-controls w-[168px] flex items-center justify-end text-tx-secondary">
<native-tooltip :content="t('window.minimize')">
<button v-show="isMinimizable"
class="flex items-center justify-center cursor-pointer w-[40px] h-[40px] hover:bg-[#999] hover:text-[#fff]"
@click="minimizeWindow">
<iconify-icon icon="material-symbols:check-indeterminate-small" :color="color" :width="btnSize"
:height="btnSize" />
</button>
</native-tooltip>
<native-tooltip :content="isMaximized ? t('window.restore') : t('window.maximize')">
<button v-show="isMaximizable"
class="flex items-center justify-center cursor-pointer w-[40px] h-[40px] hover:bg-[#999] hover:text-[#fff]"
@click="maximizeWindow">
<iconify-icon icon="material-symbols:chrome-maximize-outline-sharp" :color="color" :width="btnSize"
:height="btnSize" v-show="!isMaximized" />
<iconify-icon icon="material-symbols:chrome-restore-outline-sharp" :color="color" :width="btnSize"
:height="btnSize" v-show="isMaximized" />
</button>
</native-tooltip>
<native-tooltip :content="t('window.close')">
<button v-show="isClosable"
class="flex items-center justify-center cursor-pointer w-[40px] h-[40px] hover:bg-[#ff0000] hover:text-[#fff]"
@click="handleClose">
<iconify-icon icon="material-symbols:close" :color="color" :width="btnSize" :height="btnSize" />
</button>
</native-tooltip>
</div>
</header>
</template>
<script setup lang="ts">
import { Icon as IconifyIcon } from '@iconify/vue'
import { useWinManager } from '@hooks/useWinManager'
import NativeTooltip from '@components/NativeTooltip/index.vue'
interface HeaderBarProps {
title?: string;
isMaximizable?: boolean;
isMinimizable?: boolean;
isClosable?: boolean;
color?: string;
}
defineOptions({ name: 'HeaderBar', color: '#525866' })
withDefaults(defineProps<HeaderBarProps>(), {
isMaximizable: true,
isMinimizable: true,
isClosable: true,
})
const emit = defineEmits(['close']);
const { t } = useI18n();
const btnSize = 16;
const {
isMaximized,
closeWindow,
minimizeWindow,
maximizeWindow
} = useWinManager();
function handleClose() {
emit('close');
closeWindow();
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,60 @@
<template>
<!-- macOS: just drag region -->
<div v-if="platform === 'darwin'" class="drag-region h-10 shrink-0 border-b border-b-gray-300" style="background: transparent;" />
<!-- Linux: no custom title bar -->
<template v-else-if="platform !== 'win32'" />
<!-- Windows: custom controls -->
<div v-else class="drag-region flex h-10 shrink-0 items-center justify-end border-b" style="background: transparent;">
<div class="no-drag flex h-full">
<button
@click="handleMinimize"
class="flex h-full w-11 items-center justify-center text-[#525866] hover:bg-[#999] hover:text-white transition-colors"
title="Minimize"
>
<Minus class="h-4 w-4" />
</button>
<button
@click="handleMaximize"
class="flex h-full w-11 items-center justify-center text-[#525866] hover:bg-[#999] hover:text-white transition-colors"
:title="maximized ? 'Restore' : 'Maximize'"
>
<Copy v-if="maximized" class="h-3.5 w-3.5" />
<Square v-else class="h-3.5 w-3.5" />
</button>
<button
@click="handleClose"
class="flex h-full w-11 items-center justify-center text-[#525866] hover:bg-[#ff0000] hover:text-white transition-colors"
title="Close"
>
<X class="h-4 w-4" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Minus, Square, Copy, X } from '@lucide/vue'
const platform = (window as any).api?.platform ?? ''
const maximized = ref(false)
onMounted(async () => {
maximized.value = await (window as any).api.windowIsMaximized()
})
const handleMinimize = () => {
(window as any).api.windowMinimize()
}
const handleMaximize = async () => {
await (window as any).api.windowMaximize()
maximized.value = await (window as any).api.windowIsMaximized()
}
const handleClose = () => {
(window as any).api.windowClose()
}
</script>

View File

@@ -1,24 +1,29 @@
<template>
<div class="bg-color h-screen flex flex-col">
<header-bar>
<drag-region class="w-full" />
</header-bar>
<title-bar v-if="platform !== 'linux'" />
<main class="box-border w-full h-[calc(100vh-40px)] flex pt-[8px] pb-[8px] pl-[8px] ">
<main
class="box-border w-full flex pt-2 pb-2 pl-2"
:style="{ height: platform === 'linux' ? '100vh' : 'calc(100vh - 40px)' }"
>
<div class="flex-1 flex">
<slot />
</div>
<SideMenus />
</main>
</div>
</template>
<script setup lang="ts" name="Layout">
import { computed } from 'vue'
import SideMenus from '@src/components/SideMenus/index.vue'
import TitleBar from '@components/layout/TitleBar/index.vue'
const platform = computed(() => (window as any).api?.platform ?? '')
</script>
<style scoped>
.bg-color {
background: linear-gradient(180deg, #EFF6FF 0%, #F5F7FA 40%);
}
</style>
</style>