Browse Source

feat: electron control buttons

electron-v5
Netfan 1 year ago
parent
commit
20f902ed2c
  1. 3
      packages/@core/base/icons/src/lucide.ts
  2. 7
      packages/@core/base/typings/electron.d.ts
  3. 16
      packages/@core/ui-kit/layout-ui/src/components/layout-header.vue
  4. 3
      packages/effects/layouts/package.json
  5. 10
      packages/effects/layouts/src/authentication/toolbar.vue
  6. 19
      packages/effects/layouts/src/basic/header/header.vue
  7. 16
      packages/effects/layouts/src/widgets/app-close/app-close.vue
  8. 1
      packages/effects/layouts/src/widgets/app-close/index.ts
  9. 30
      packages/effects/layouts/src/widgets/app-maximize/app-maximize.vue
  10. 1
      packages/effects/layouts/src/widgets/app-maximize/index.ts
  11. 16
      packages/effects/layouts/src/widgets/app-minimize/app-minimize.vue
  12. 1
      packages/effects/layouts/src/widgets/app-minimize/index.ts
  13. 3
      packages/effects/layouts/src/widgets/index.ts
  14. 3
      packages/effects/layouts/tsconfig.json
  15. 45
      playground/electron/main.ts
  16. 1
      playground/electron/preload.ts
  17. 4
      pnpm-lock.yaml

3
packages/@core/base/icons/src/lucide.ts

@ -45,6 +45,7 @@ export {
Menu,
Minimize,
Minimize2,
Minus,
MoonStar,
Palette,
PanelLeft,
@ -58,6 +59,8 @@ export {
Settings,
Shrink,
Square,
SquareArrowDownLeft,
SquareArrowUpRight,
SquareCheckBig,
SquareMinus,
Sun,

7
packages/@core/base/typings/electron.d.ts

@ -1,6 +1,11 @@
import type { IpcRendererEvent } from 'electron';
export type IpcRendererInvoke = 'open-win';
export type IpcRendererInvoke =
| 'app-close'
| 'app-maximize'
| 'app-minimize'
| 'is-maximized'
| 'open-win';
declare global {
interface Window {

16
packages/@core/ui-kit/layout-ui/src/components/layout-header.vue

@ -64,7 +64,7 @@ const logoStyle = computed((): CSSProperties => {
<header
:class="theme"
:style="style"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
class="app-header border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<slot name="logo"></slot>
@ -75,3 +75,17 @@ const logoStyle = computed((): CSSProperties => {
<slot></slot>
</header>
</template>
<style lang="scss" scoped>
.bg-header {
app-region: drag;
user-select: none;
:deep(.cursor-pointer),
:deep(.vben-sub-menu),
:deep(.vben-menu-item),
:deep(button),
:deep(a) {
app-region: no-drag;
}
}
</style>

3
packages/effects/layouts/package.json

@ -39,5 +39,8 @@
"@vueuse/core": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
},
"devDependencies": {
"@vben-core/typings": "workspace:*"
}
}

10
packages/effects/layouts/src/authentication/toolbar.vue

@ -6,6 +6,9 @@ import { computed } from 'vue';
import { preferences } from '@vben/preferences';
import {
AppClose,
AppMaxmize,
AppMinmize,
AuthenticationColorToggle,
AuthenticationLayoutToggle,
LanguageToggle,
@ -28,6 +31,8 @@ const showColor = computed(() => props.toolbarList.includes('color'));
const showLayout = computed(() => props.toolbarList.includes('layout'));
const showLanguage = computed(() => props.toolbarList.includes('language'));
const showTheme = computed(() => props.toolbarList.includes('theme'));
const isElectron = window?.ipcRenderer !== undefined;
</script>
<template>
@ -45,5 +50,10 @@ const showTheme = computed(() => props.toolbarList.includes('theme'));
<!-- Always show Language and Theme toggles -->
<LanguageToggle v-if="showLanguage && preferences.widget.languageToggle" />
<ThemeToggle v-if="showTheme && preferences.widget.themeToggle" />
<template v-if="isElectron">
<AppMinmize />
<AppMaxmize />
<AppClose />
</template>
</div>
</template>

19
packages/effects/layouts/src/basic/header/header.vue

@ -9,6 +9,9 @@ import { useAccessStore } from '@vben/stores';
import { VbenFullScreen, VbenIconButton } from '@vben-core/shadcn-ui';
import {
AppClose,
AppMaxmize,
AppMinmize,
GlobalSearch,
LanguageToggle,
PreferencesButton,
@ -41,6 +44,13 @@ const { refresh } = useRefresh();
const rightSlots = computed(() => {
const list = [{ index: REFERENCE_VALUE + 100, name: 'user-dropdown' }];
if (window.ipcRenderer) {
list.push(
{ index: REFERENCE_VALUE + 110, name: 'app-minimize' },
{ index: REFERENCE_VALUE + 120, name: 'app-maximize' },
{ index: REFERENCE_VALUE + 120, name: 'app-close' },
);
}
if (preferences.widget.globalSearch) {
list.push({
index: REFERENCE_VALUE,
@ -166,6 +176,15 @@ function clearPreferencesAndLogout() {
<template v-else-if="slot.name === 'fullscreen'">
<VbenFullScreen class="mr-1" />
</template>
<template v-else-if="slot.name === 'app-minimize'">
<AppMinmize class="mr-1" />
</template>
<template v-else-if="slot.name === 'app-maximize'">
<AppMaxmize class="mr-1" />
</template>
<template v-else-if="slot.name === 'app-close'">
<AppClose class="mr-1" />
</template>
</slot>
</template>
</div>

16
packages/effects/layouts/src/widgets/app-close/app-close.vue

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { X } from '@vben/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
function handleAppClose() {
window.ipcRenderer?.invoke('app-close');
}
</script>
<template>
<div class="flex-center mr-2 h-full" @click.stop="handleAppClose()">
<VbenIconButton class="bell-button text-foreground relative">
<X class="size-4" />
</VbenIconButton>
</div>
</template>

1
packages/effects/layouts/src/widgets/app-close/index.ts

@ -0,0 +1 @@
export { default as AppClose } from './app-close.vue';

30
packages/effects/layouts/src/widgets/app-maximize/app-maximize.vue

@ -0,0 +1,30 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { SquareArrowDownLeft, SquareArrowUpRight } from '@vben/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
const isMaximized = ref(false);
if (window.ipcRenderer) {
onMounted(async () => {
isMaximized.value = await window.ipcRenderer.invoke('is-maximized');
window.ipcRenderer.on('maximize-changed', (_, maximized) => {
isMaximized.value = maximized;
});
});
}
function handleAppMaximize() {
window.ipcRenderer?.invoke('app-maximize');
}
</script>
<template>
<div class="flex-center mr-2 h-full" @click.stop="handleAppMaximize()">
<VbenIconButton class="bell-button text-foreground relative">
<SquareArrowDownLeft v-if="isMaximized" class="size-4" />
<SquareArrowUpRight v-else class="size-4" />
</VbenIconButton>
</div>
</template>

1
packages/effects/layouts/src/widgets/app-maximize/index.ts

@ -0,0 +1 @@
export { default as AppMaxmize } from './app-maximize.vue';

16
packages/effects/layouts/src/widgets/app-minimize/app-minimize.vue

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { Minus } from '@vben/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
function handleAppMinimize() {
window.ipcRenderer?.invoke('app-minimize');
}
</script>
<template>
<div class="flex-center mr-2 h-full" @click.stop="handleAppMinimize()">
<VbenIconButton class="bell-button text-foreground relative">
<Minus class="size-4" />
</VbenIconButton>
</div>
</template>

1
packages/effects/layouts/src/widgets/app-minimize/index.ts

@ -0,0 +1 @@
export { default as AppMinmize } from './app-minimize.vue';

3
packages/effects/layouts/src/widgets/index.ts

@ -1,3 +1,6 @@
export * from './app-close';
export * from './app-maximize';
export * from './app-minimize';
export { default as Breadcrumb } from './breadcrumb.vue';
export * from './check-updates';
export { default as AuthenticationColorToggle } from './color-toggle.vue';

3
packages/effects/layouts/tsconfig.json

@ -1,6 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"compilerOptions": {
"types": ["@vben-core/typings/electron"]
},
"include": ["src"],
"exclude": ["node_modules"]
}

45
playground/electron/main.ts

@ -41,8 +41,10 @@ const indexHtml = path.join(RENDERER_DIST, 'index.html');
async function createWindow() {
win = new BrowserWindow({
autoHideMenuBar: true,
frame: false,
height: 900,
icon: path.join(process.env.VITE_PUBLIC as string, 'favicon.ico'),
movable: true,
show: false,
title: 'Main window',
webPreferences: {
@ -60,6 +62,14 @@ async function createWindow() {
win?.show(); // 显示窗口
});
win.on('maximize', () => {
win?.webContents.send('maximize-changed', true);
});
win.on('unmaximize', () => {
win?.webContents.send('maximize-changed', false);
});
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL);
} else {
@ -132,6 +142,7 @@ app.on('activate', () => {
// New window example arg: new windows url
ipcMain.handle('open-win', (_, arg) => {
const childWindow = new BrowserWindow({
frame: false,
webPreferences: {
contextIsolation: true,
nodeIntegration: true,
@ -142,8 +153,40 @@ ipcMain.handle('open-win', (_, arg) => {
if (VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`);
childWindow.webContents.openDevTools();
} else {
childWindow.loadFile(indexHtml, { hash: arg });
}
});
ipcMain.handle('app-minimize', (event) => {
const browserWindow = BrowserWindow.fromWebContents(event.sender);
if (browserWindow) {
browserWindow.minimize();
}
});
ipcMain.handle('app-maximize', (event) => {
const browserWindow = BrowserWindow.fromWebContents(event.sender);
if (browserWindow) {
if (browserWindow.isMaximized()) {
browserWindow.restore();
} else {
browserWindow.maximize();
}
}
});
ipcMain.handle('app-close', (event) => {
const browserWindow = BrowserWindow.fromWebContents(event.sender);
if (browserWindow) {
browserWindow.close();
}
});
ipcMain.handle('is-maximized', (event) => {
const browserWindow = BrowserWindow.fromWebContents(event.sender);
if (browserWindow) {
return browserWindow.isMaximized();
}
return false;
});

1
playground/electron/preload.ts

@ -20,7 +20,6 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
const [channel, ...omit] = args;
return ipcRenderer.send(channel, ...omit);
},
// You can expose other APTs you need here.
// ...
});

4
pnpm-lock.yaml

@ -1687,6 +1687,10 @@ importers:
vue-router:
specifier: 'catalog:'
version: 4.5.0(vue@3.5.13(typescript@5.8.3))
devDependencies:
'@vben-core/typings':
specifier: workspace:*
version: link:../../@core/base/typings
packages/effects/plugins:
dependencies:

Loading…
Cancel
Save