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:
@@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div class="drag-region">
|
||||
<slot>
|
||||
<span class="_placeholder">_hidden</span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
._placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
60
src/components/Layout/TitleBar/index.vue
Normal file
60
src/components/Layout/TitleBar/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
export function useWinManager() {
|
||||
const isMaximized = ref(false)
|
||||
|
||||
function closeWindow() {
|
||||
window.api.closeWindow();
|
||||
}
|
||||
|
||||
function minimizeWindow() {
|
||||
window.api.minimizeWindow();
|
||||
}
|
||||
|
||||
function maximizeWindow() {
|
||||
window.api.maximizeWindow();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
window.api.viewIsReady();
|
||||
isMaximized.value = await window.api.isWindowMaximized();
|
||||
window.api.onWindowMaximized((_isMaximized: boolean) => isMaximized.value = _isMaximized);
|
||||
})
|
||||
|
||||
return {
|
||||
isMaximized,
|
||||
closeWindow,
|
||||
minimizeWindow,
|
||||
maximizeWindow
|
||||
}
|
||||
};
|
||||
|
||||
export default useWinManager;
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
export enum IPC_EVENTS {
|
||||
EXTERNAL_OPEN = 'external-open',
|
||||
WINDOW_MINIMIZE = 'window-minimize',
|
||||
WINDOW_MAXIMIZE = 'window-maximize',
|
||||
WINDOW_CLOSE = 'window-close',
|
||||
IS_WINDOW_MAXIMIZED = 'is-window-maximized',
|
||||
APP_SET_FRAMELESS = 'app:set-frameless',
|
||||
APP_LOAD_PAGE = 'app:load-page',
|
||||
TAB_CREATE = 'tab:create',
|
||||
|
||||
@@ -17,13 +17,9 @@ import "./styles/index.css";
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
// 引入全局组件
|
||||
import HeaderBar from '@components/HeaderBar/index.vue'
|
||||
import DragRegion from '@components/DragRegion/index.vue'
|
||||
import Layout from '@components/Layout/index.vue'
|
||||
|
||||
const components: Plugin = (app) => {
|
||||
app.component('HeaderBar', HeaderBar);
|
||||
app.component('DragRegion', DragRegion);
|
||||
app.component('Layout', Layout);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
<template>
|
||||
<div class="h-screen login-bg flex flex-col">
|
||||
<header-bar color="#fff">
|
||||
<drag-region class="w-full" />
|
||||
</header-bar>
|
||||
|
||||
<main class="box-border p-[8px] flex-auto flex ">
|
||||
<div class="w-[836px] box-border bg-white rounded-2xl p-[32px] flex flex-col">
|
||||
<main class="box-border pl-2 pr-2 pb-2 pt-11 flex-auto flex ">
|
||||
<div class="w-209 box-border bg-white rounded-2xl p-8 flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<img class="w-[48px] h-[48px]" src="@assets/images/login/blue_logo.png" />
|
||||
<img class="w-12 h-12" src="@assets/images/login/blue_logo.png" />
|
||||
|
||||
<!-- <span class="ml-auto text-[14px] text-gray-600">没有账号?</span>
|
||||
<button
|
||||
class="bg-sky-50 rounded-[8px] text-[14px] text-sky-600 px-[12px] py-[6px] focus-visible:outline-none cursor-pointer">注册</button> -->
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center justify-center mb-[24px] box-border pt-[40px]">
|
||||
<img class="w-[80px] h-[80px] mb-[12px]" src="@assets/images/login/user_icon.png" />
|
||||
<div class="text-[24px] font-500 text-gray-800 line-height-[32px] mb-[4px]">{{ t('login.title') }}</div>
|
||||
<div class="flex flex-col items-center justify-center mb-6 box-border pt-10">
|
||||
<img class="w-20 h-20 mb-3" src="@assets/images/login/user_icon.png" />
|
||||
<div class="text-[24px] font-500 text-gray-800 line-height-[32px] mb-1">{{ t('login.title') }}</div>
|
||||
<div class="text-[16px] text-gray-500 line-height-[24px]">{{ t('login.subtitle') }}</div>
|
||||
</div>
|
||||
|
||||
<el-form class="w-[392px] ml-auto mr-auto" ref="formRef" :rules="rules" :model="form" label-position="top"
|
||||
<el-form class="w-98 ml-auto mr-auto" ref="formRef" :rules="rules" :model="form" label-position="top"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item prop="username">
|
||||
<div class="text-[14px] text-gray-600">{{ t('login.username') }}</div>
|
||||
<el-input class="h-[40px]" v-model.trim="form.username" :placeholder="t('login.usernamePlaceholder')" clearable autocomplete="off">
|
||||
<el-input class="h-10" v-model.trim="form.username" :placeholder="t('login.usernamePlaceholder')" clearable autocomplete="off">
|
||||
<template #prefix>
|
||||
<RiUser3Fill size="20px" color="#99A0AE" />
|
||||
</template>
|
||||
@@ -32,7 +28,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<div class="text-[14px] text-gray-600">{{ t('login.password') }}</div>
|
||||
<el-input class="h-[40px]" v-model.trim="form.password" type="password" :placeholder="t('login.passwordPlaceholder')" clearable
|
||||
<el-input class="h-10" v-model.trim="form.password" type="password" :placeholder="t('login.passwordPlaceholder')" clearable
|
||||
autocomplete="off">
|
||||
<template #prefix>
|
||||
<RiKey2Fill size="20px" color="#99A0AE" />
|
||||
@@ -44,7 +40,7 @@
|
||||
<span class="text-[14px] text-gray-600">{{ t('login.code') }}</span>
|
||||
<el-input v-model.trim="form.code" :placeholder="t('login.codePlaceholder')" autocomplete="off">
|
||||
<template #suffix>
|
||||
<img class="w-[80px] h-[38px] cursor-pointer" :src="imgSrc" @click="getVerifyCode" />
|
||||
<img class="w-20 h-9.5 cursor-pointer" :src="imgSrc" @click="getVerifyCode" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -60,7 +56,7 @@
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button type="button"
|
||||
class="w-full py-2 bg-blue-600 cursor-pointer text-white rounded-[8px] hover:bg-blue-700 disabled:bg-blue-300"
|
||||
class="w-full py-2 bg-blue-600 cursor-pointer text-white rounded-lg hover:bg-blue-700 disabled:bg-blue-300"
|
||||
:loading="loading" @click="onSubmit">
|
||||
{{ t('login.loginButton') }}
|
||||
</button>
|
||||
@@ -81,7 +77,7 @@
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<img class="w-[540px]" src="@assets/images/login/logo.png" />
|
||||
<img class="w-135" src="@assets/images/login/logo.png" />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user