featlectron): 实(e现自定义窗口控制和无边框窗口

- 添加窗口最小化、最大化、关闭和状态检测 API- 实现无边框窗口模式并添加窗口控制按钮
- 更新导航栏 UI,添加登录按钮和窗口控制区域
- 调整 Amazon、Rakuten 和 Zebra 仪表板样式
- 移除面包屑导航并调整布局结构
- 更新用户头像样式和 VIP 状态卡片背景渐变
- 添加窗口拖拽区域和用户选择禁用样式
This commit is contained in:
2025-10-24 15:06:47 +08:00
parent 3a76aaa3c0
commit 35c9fc205a
10 changed files with 220 additions and 56 deletions

View File

@@ -250,6 +250,7 @@ function createWindow() {
width: 1280,
height: 800,
show: false, //
frame: false,
autoHideMenuBar: true,
icon: getIconPath(),
backgroundColor: '#f5f5f5',
@@ -726,6 +727,33 @@ ipcMain.handle('set-launch-config', (event, launchConfig: { autoLaunch: boolean;
// 刷新页面
ipcMain.handle('reload', () => mainWindow?.webContents.reload());
// 窗口控制 API
ipcMain.handle('window-minimize', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.minimize();
}
});
ipcMain.handle('window-maximize', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
}
});
ipcMain.handle('window-close', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.close();
}
});
ipcMain.handle('window-is-maximized', () => {
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.isMaximized() : false;
});
async function getFileSize(url: string): Promise<number> {
return new Promise((resolve) => {

View File

@@ -37,6 +37,12 @@ const electronAPI = {
// 刷新页面 API
reload: () => ipcRenderer.invoke('reload'),
// 窗口控制 API
windowMinimize: () => ipcRenderer.invoke('window-minimize'),
windowMaximize: () => ipcRenderer.invoke('window-maximize'),
windowClose: () => ipcRenderer.invoke('window-close'),
windowIsMaximized: () => ipcRenderer.invoke('window-is-maximized'),
onDownloadProgress: (callback: (progress: any) => void) => {
ipcRenderer.removeAllListeners('download-progress')
ipcRenderer.on('download-progress', (event, progress) => callback(progress))

View File

@@ -629,7 +629,8 @@ onUnmounted(() => {
@open-device="openDeviceManager"
@open-settings="openSettings"
@open-account-manager="openAccountManager"
@check-update="handleCheckUpdate"/>
@check-update="handleCheckUpdate"
@show-login="showAuthDialog = true"/>
<div class="content-body">
<div
class="dashboard-home"
@@ -803,14 +804,12 @@ onUnmounted(() => {
align-items: center;
justify-content: center;
padding: 12px 0;
border-bottom: 1px solid #e8eaec;
margin: 0 0 12px 0;
}
.user-avatar img {
width: 50px;
height: 50px;
border-radius: 50%;
width: 90px;
object-fit: contain;
background: #ffffff;
}
@@ -997,14 +996,13 @@ onUnmounted(() => {
.vip-status-card {
margin-top: auto;
width: 100%;
min-height: 64px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
box-sizing: border-box;
background: linear-gradient(135deg, #FFFAF0 0%, #FFE4B5 50%, #FFD700 100%);
background: linear-gradient(90deg, #FFF7CC 0%, #FFEB80 100%);
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
transition: all 0.3s ease;
position: relative;
@@ -1025,7 +1023,7 @@ onUnmounted(() => {
.vip-status-card.vip-active,
.vip-status-card.vip-normal,
.vip-status-card.vip-warning {
background: linear-gradient(135deg, #FFFAF0 0%, #FFE4B5 50%, #FFD700 100%);
background: linear-gradient(90deg, #FFF7CC 0%, #FFEB80 100%);
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
}

View File

@@ -492,8 +492,8 @@ onMounted(async () => {
</template>
<style scoped>
.amazon-root { position: absolute; inset: 0; background: #f5f5f5; padding: 12px; box-sizing: border-box; }
.main-container { background: #fff; border-radius: 4px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); height: 100%; display: flex; flex-direction: column; }
.amazon-root { position: absolute; inset: 0; background: #fff; box-sizing: border-box; }
.main-container { height: 100%; display: flex; flex-direction: column; padding: 12px; box-sizing: border-box; }
/* 顶部标签栏 */
.top-tabs { display: flex; margin-bottom: 8px; }
@@ -587,7 +587,7 @@ onMounted(async () => {
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.pagination-fixed { flex-shrink: 0; padding: 8px 12px; background: #fff; display: flex; justify-content: flex-end; margin-top: 8px; }
.pagination-fixed { flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
.empty-tip { text-align: center; color: #909399; padding: 16px 0; }
.import-section[draggable], .import-section.drag-active { border: 1px dashed #409EFF; border-radius: 6px; }

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { ArrowLeft, ArrowRight, Refresh, Setting } from '@element-plus/icons-vue'
import { computed, ref, onMounted } from 'vue'
import { Refresh, Setting, Minus, CloseBold } from '@element-plus/icons-vue'
interface Props {
canGoBack: boolean
@@ -20,15 +20,31 @@ interface Emits {
(e: 'open-settings'): void
(e: 'open-account-manager'): void
(e: 'check-update'): void
(e: 'show-login'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const isMaximized = ref(false)
const displayUsername = computed(() => {
return props.isAuthenticated ? props.currentUsername : '未登录'
})
async function handleMinimize() {
await (window as any).electronAPI.windowMinimize()
}
async function handleMaximize() {
await (window as any).electronAPI.windowMaximize()
isMaximized.value = await (window as any).electronAPI.windowIsMaximized()
}
async function handleClose() {
await (window as any).electronAPI.windowClose()
}
function handleCommand(command: string) {
switch (command) {
case 'logout':
@@ -48,6 +64,10 @@ function handleCommand(command: string) {
break
}
}
onMounted(async () => {
isMaximized.value = await (window as any).electronAPI.windowIsMaximized()
})
</script>
<template>
@@ -55,21 +75,17 @@ function handleCommand(command: string) {
<div class="navbar-left">
<div class="nav-controls">
<button class="nav-btn" title="后退" @click="$emit('go-back')" :disabled="!canGoBack">
<el-icon><ArrowLeft /></el-icon>
<svg viewBox="0 0 24 24" class="arrow-icon">
<path d="M15 18l-6-6 6-6" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="nav-btn" title="前进" @click="$emit('go-forward')" :disabled="!canGoForward">
<el-icon><ArrowRight /></el-icon>
<svg viewBox="0 0 24 24" class="arrow-icon">
<path d="M9 18l6-6-6-6" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
<div class="navbar-center">
<div class="breadcrumbs">
<span>首页</span>
<span class="separator">></span>
<span>{{ activeMenu }}</span>
</div>
</div>
<div class="navbar-right">
<button class="nav-btn-round" title="刷新" @click="$emit('reload')">
<el-icon><Refresh /></el-icon>
</button>
@@ -102,6 +118,34 @@ function handleCommand(command: string) {
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 登录/注册按钮 -->
<button v-if="!isAuthenticated" class="login-btn" @click="$emit('show-login')">
登录/注册
</button>
</div>
<div class="navbar-center">
<div class="breadcrumbs">
<span>首页</span>
<span class="separator">/</span>
<span>{{ activeMenu }}</span>
</div>
</div>
<div class="navbar-right">
<!-- 窗口控制按钮 -->
<div class="window-controls">
<button class="window-btn window-btn-minimize" title="最小化" @click="handleMinimize">
<el-icon><Minus /></el-icon>
</button>
<button class="window-btn window-btn-maximize" title="最大化" @click="handleMaximize">
<svg viewBox="0 0 12 12" class="maximize-icon">
<rect x="2" y="2" width="8" height="8" stroke="currentColor" fill="none" stroke-width="1"/>
</svg>
</button>
<button class="window-btn window-btn-close" title="关闭" @click="handleClose">
<el-icon><CloseBold /></el-icon>
</button>
</div>
</div>
</div>
</template>
@@ -116,12 +160,16 @@ function handleCommand(command: string) {
background: #ffffff;
border-bottom: 1px solid #e8eaec;
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
-webkit-app-region: drag;
user-select: none;
}
.navbar-left {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
-webkit-app-region: no-drag;
}
.navbar-center {
@@ -135,35 +183,49 @@ function handleCommand(command: string) {
align-items: center;
gap: 8px;
flex: 0 0 auto;
-webkit-app-region: no-drag;
}
.nav-controls {
display: flex;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
gap: 4px;
}
.nav-btn {
width: 32px;
width: 28px;
height: 28px;
border: none;
background: #fff;
background: transparent;
cursor: pointer;
font-size: 14px;
font-size: 16px;
color: #606266;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
outline: none;
border-radius: 4px;
}
.arrow-icon {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.arrow-icon path {
stroke: currentColor;
}
.nav-btn:hover:not(:disabled) {
background: #f5f7fa;
background: rgba(0, 0, 0, 0.05);
color: #409EFF;
}
.nav-btn:hover:not(:disabled) .arrow-icon path {
stroke: #409EFF;
}
.nav-btn:focus,
.nav-btn:active {
outline: none;
@@ -172,23 +234,22 @@ function handleCommand(command: string) {
.nav-btn:disabled {
cursor: not-allowed;
opacity: 0.5;
background: #f5f5f5;
color: #c0c4cc;
background: transparent;
color: #d0d0d0;
}
.nav-btn:not(:last-child) {
border-right: 1px solid #dcdfe6;
.nav-btn:disabled .arrow-icon path {
stroke: #d0d0d0;
}
.nav-btn-round {
width: 28px;
height: 28px;
border: 1px solid #dcdfe6;
border-radius: 50%;
background: #fff;
border: none;
border-radius: 4px;
background: transparent;
cursor: pointer;
font-size: 12px;
font-size: 16px;
color: #606266;
display: flex;
align-items: center;
@@ -198,9 +259,8 @@ function handleCommand(command: string) {
}
.nav-btn-round:hover {
background: #f5f7fa;
background: rgba(0, 0, 0, 0.05);
color: #409EFF;
border-color: #c6e2ff;
}
.nav-btn-round:focus,
@@ -216,9 +276,82 @@ function handleCommand(command: string) {
}
.separator {
margin: 0 8px;
margin: 0 6px;
color: #c0c4cc;
font-size: 12px;
font-size: 14px;
}
/* 窗口控制按钮 */
.window-controls {
display: flex;
align-items: center;
gap: 4px;
margin-left: 8px;
}
.window-btn {
width: 32px;
height: 28px;
border: none;
background: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #606266;
transition: all 0.2s ease;
outline: none;
padding: 0;
margin: 0;
border-radius: 4px;
}
.window-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.window-btn:active {
background: rgba(0, 0, 0, 0.1);
}
.window-btn-close:hover {
background: #e81123;
color: #ffffff;
}
.window-btn-close:active {
background: #f1707a;
}
.maximize-icon {
width: 12px;
height: 12px;
}
/* 登录/注册按钮 */
.login-btn {
height: 28px;
padding: 0 12px;
border: none;
border-radius: 4px;
background: #1677FF;
color: #fff;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
outline: none;
white-space: nowrap;
margin-left: 8px;
}
.login-btn:hover {
background: #0d5ed6;
}
.login-btn:active {
background: #0c54c2;
}
</style>
@@ -229,7 +362,6 @@ function handleCommand(command: string) {
padding: 4px 0 !important;
border-radius: 12px !important;
margin-top: 4px !important;
margin-right: 8px !important;
}
.settings-dropdown .username-item {

View File

@@ -651,19 +651,16 @@ onMounted(loadLatest)
.rakuten-root {
position: absolute;
inset: 0;
background: #f5f5f5;
padding: 12px;
background: #fff;
box-sizing: border-box;
}
.main-container {
background: #fff;
border-radius: 4px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
height: 100%;
display: flex;
flex-direction: column;
padding: 12px;
box-sizing: border-box;
}
.body-layout { display: flex; gap: 12px; height: 100%; }
@@ -859,11 +856,10 @@ onMounted(loadLatest)
.pagination-fixed {
flex-shrink: 0;
padding: 8px 12px;
padding: 8px 12px 0 12px;
background: #fff;
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
.pagination-fixed :deep(.el-pager li.is-active) {

View File

@@ -575,8 +575,8 @@ export default {
</script>
<style scoped>
.zebra-root { position: absolute; inset: 0; background: #f5f5f5; padding: 12px; box-sizing: border-box; }
.layout { background: #fff; border-radius: 4px; padding: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); height: 100%; display: grid; grid-template-columns: 220px 1fr; gap: 12px; }
.zebra-root { position: absolute; inset: 0; background: #fff; box-sizing: border-box; }
.layout { height: 100%; display: grid; grid-template-columns: 220px 1fr; gap: 12px; padding: 12px; box-sizing: border-box; }
.aside { border: 1px solid #ebeef5; border-radius: 4px; padding: 10px; display: flex; flex-direction: column; transition: width 0.2s ease; }
.aside.collapsed { width: 56px; overflow: hidden; }
.aside-header { display: flex; justify-content: flex-start; align-items: center; font-weight: 600; color: #606266; margin-bottom: 8px; }
@@ -648,7 +648,7 @@ export default {
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.pagination-fixed { position: sticky; bottom: 0; z-index: 2; padding: 8px 12px; background: #fff; display: flex; justify-content: flex-end; margin-top: 8px; }
.pagination-fixed { position: sticky; bottom: 0; z-index: 2; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
.tag { display: inline-block; padding: 0 6px; margin-left: 6px; font-size: 12px; background: #ecf5ff; color: #409EFF; border-radius: 3px; }
.empty-abs { position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -14,6 +14,10 @@ export default interface ElectronApi {
getUpdateStatus: () => Promise<{ downloadedFilePath: string | null; isDownloading: boolean; downloadProgress: any; isPackaged: boolean }>
onUpdateLog: (callback: (log: string) => void) => void
removeUpdateLogListener: () => void
windowMinimize: () => Promise<void>
windowMaximize: () => Promise<void>
windowClose: () => Promise<void>
windowIsMaximized: () => Promise<boolean>
}
declare global {