refactor: 增加TitleBar组件,微调页面布局

This commit is contained in:
2025-09-03 22:44:16 +08:00
parent 54e289ce56
commit 003f150a74
8 changed files with 309 additions and 72 deletions

View File

@@ -69,6 +69,8 @@ function createWindow() {
minWidth: 800,
minHeight: 600,
icon: path.join(__dirname, '../src/assets/AUTO_MAA.ico'),
frame: false, // 去掉系统标题栏
titleBarStyle: 'hidden', // 隐藏标题栏
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
@@ -109,6 +111,33 @@ ipcMain.handle('open-dev-tools', () => {
}
})
// 窗口控制
ipcMain.handle('window-minimize', () => {
if (mainWindow) {
mainWindow.minimize()
}
})
ipcMain.handle('window-maximize', () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
}
})
ipcMain.handle('window-close', () => {
if (mainWindow) {
mainWindow.close()
}
})
ipcMain.handle('window-is-maximized', () => {
return mainWindow ? mainWindow.isMaximized() : false
})
ipcMain.handle('select-folder', async () => {
if (!mainWindow) return null
const result = await dialog.showOpenDialog(mainWindow, {

View File

@@ -11,6 +11,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
selectFile: (filters?: any[]) => ipcRenderer.invoke('select-file', filters),
openUrl: (url: string) => ipcRenderer.invoke('open-url', url),
// 窗口控制
windowMinimize: () => ipcRenderer.invoke('window-minimize'),
windowMaximize: () => ipcRenderer.invoke('window-maximize'),
windowClose: () => ipcRenderer.invoke('window-close'),
windowIsMaximized: () => ipcRenderer.invoke('window-is-maximized'),
// 初始化相关API
checkEnvironment: () => ipcRenderer.invoke('check-environment'),
downloadPython: (mirror?: string) => ipcRenderer.invoke('download-python', mirror),

View File

@@ -4,6 +4,7 @@ import { useRoute } from 'vue-router'
import { ConfigProvider } from 'ant-design-vue'
import { useTheme } from './composables/useTheme.ts'
import AppLayout from './components/AppLayout.vue'
import TitleBar from './components/TitleBar.vue'
import zhCN from 'ant-design-vue/es/locale/zh_CN'
import { logger } from '@/utils/logger'
@@ -24,8 +25,11 @@ onMounted(() => {
<ConfigProvider :theme="antdTheme" :locale="zhCN">
<!-- 初始化页面使用全屏布局 -->
<router-view v-if="isInitializationPage" />
<!-- 其他页面使用应用布局 -->
<AppLayout v-else />
<!-- 其他页面使用带标题栏的应用布局 -->
<div v-else class="app-container">
<TitleBar />
<AppLayout />
</div>
</ConfigProvider>
</template>
@@ -33,4 +37,9 @@ onMounted(() => {
* {
box-sizing: border-box;
}
.app-container {
height: 100vh;
overflow: hidden;
}
</style>

View File

@@ -7,14 +7,14 @@
:width="180"
:collapsed-width="60"
:theme="isDark ? 'dark' : 'light'"
style="height: 100vh; position: fixed; left: 0; top: 0; z-index: 100"
style="height: calc(100vh - 32px); position: fixed; left: 0; top: 32px; z-index: 100"
>
<div class="sider-content">
<!-- Logo -->
<div class="logo" @click="toggleCollapse">
<img src="/src/assets/AUTO_MAA.ico" alt="AUTO_MAA" class="logo-image" />
<span class="logo-text" :class="{ 'text-hidden': collapsed }">AUTO_MAA</span>
</div>
<!-- &lt;!&ndash; 折叠按钮 &ndash;&gt;-->
<!-- <div class="collapse-trigger" @click="toggleCollapse">-->
<!-- <MenuFoldOutlined v-if="!collapsed" />-->
<!-- <MenuUnfoldOutlined v-else />-->
<!-- </div>-->
<!-- 主菜单容器 -->
<div class="main-menu-container">
@@ -61,7 +61,7 @@
:style="{
marginLeft: collapsed ? '60px' : '180px',
transition: 'margin-left 0.2s',
height: '100vh',
height: 'calc(100vh - 32px)',
}"
>
<a-layout-content
@@ -69,7 +69,7 @@
:style="{
padding: '24px',
background: isDark ? '#141414' : '#ffffff',
height: '100vh',
height: '100%',
overflow: 'auto',
}"
>
@@ -88,6 +88,8 @@ import {
ControlOutlined,
HistoryOutlined,
SettingOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons-vue'
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
@@ -136,41 +138,33 @@ const toggleCollapse = () => {
padding-bottom: 4px; /* 关键添加3px底部内边距 */
}
/* Logo */
.logo {
/* 折叠按钮 */
.collapse-trigger {
height: 42px;
display: flex;
align-items: center;
padding: 0 12px;
justify-content: center;
margin: 4px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s;
}
.logo:hover {
background-color: rgba(255, 255, 255, 0.5);
.collapse-trigger:hover {
background-color: rgba(255, 255, 255, 0.1);
}
:deep(.ant-layout-sider-light) .logo:hover {
:deep(.ant-layout-sider-light) .collapse-trigger:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.logo-image {
width: 32px;
height: 32px;
:deep(.ant-layout-sider-dark) .collapse-trigger {
color: #fff;
}
.logo-text {
margin-left: 12px;
font-size: 16px;
font-weight: bold;
white-space: nowrap;
opacity: 1;
transition: opacity 0.2s ease;
}
.logo-text.text-hidden {
opacity: 0;
:deep(.ant-layout-sider-light) .collapse-trigger {
color: rgba(0, 0, 0, 0.88);
}
/* 主菜单容器 */

View File

@@ -0,0 +1,193 @@
<template>
<div class="title-bar" :class="{ 'title-bar-dark': isDark }">
<!-- 左侧Logo和软件名 -->
<div class="title-bar-left">
<div class="logo-section">
<img src="@/assets/AUTO_MAA.ico" alt="AUTO_MAA" class="title-logo" />
<span class="title-text">AUTO_MAA</span>
</div>
</div>
<!-- 中间可拖拽区域 -->
<div class="title-bar-center drag-region"></div>
<!-- 右侧窗口控制按钮 -->
<div class="title-bar-right">
<div class="window-controls">
<button
class="control-button minimize-button"
@click="minimizeWindow"
title="最小化"
>
<MinusOutlined />
</button>
<button
class="control-button maximize-button"
@click="toggleMaximize"
:title="isMaximized ? '还原' : '最大化'"
>
<BorderOutlined v-if="!isMaximized" />
<CopyOutlined v-else />
</button>
<button
class="control-button close-button"
@click="closeWindow"
title="关闭"
>
<CloseOutlined />
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { MinusOutlined, BorderOutlined, CopyOutlined, CloseOutlined } from '@ant-design/icons-vue'
import { useTheme } from '@/composables/useTheme'
const { isDark } = useTheme()
const isMaximized = ref(false)
const minimizeWindow = async () => {
try {
await window.electronAPI?.windowMinimize()
} catch (error) {
console.error('Failed to minimize window:', error)
}
}
const toggleMaximize = async () => {
try {
await window.electronAPI?.windowMaximize()
isMaximized.value = await window.electronAPI?.windowIsMaximized() || false
} catch (error) {
console.error('Failed to toggle maximize:', error)
}
}
const closeWindow = async () => {
try {
await window.electronAPI?.windowClose()
} catch (error) {
console.error('Failed to close window:', error)
}
}
onMounted(async () => {
try {
isMaximized.value = await window.electronAPI?.windowIsMaximized() || false
} catch (error) {
console.error('Failed to get window state:', error)
}
})
</script>
<style scoped>
.title-bar {
height: 32px;
background: #ffffff;
border-bottom: 1px solid #e8e8e8;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
position: relative;
z-index: 1000;
}
.title-bar-dark {
background: #1f1f1f;
border-bottom: 1px solid #333;
}
.title-bar-left {
display: flex;
align-items: center;
padding-left: 12px;
height: 100%;
}
.logo-section {
display: flex;
align-items: center;
gap: 8px;
}
.title-logo {
width: 20px;
height: 20px;
}
.title-text {
font-size: 13px;
font-weight: 600;
color: #333;
}
.title-bar-dark .title-text {
color: #fff;
}
.title-bar-center {
flex: 1;
height: 100%;
}
.drag-region {
-webkit-app-region: drag;
}
.title-bar-right {
display: flex;
align-items: center;
height: 100%;
}
.window-controls {
display: flex;
height: 100%;
}
.control-button {
width: 46px;
height: 32px;
border: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s;
color: #666;
font-size: 12px;
-webkit-app-region: no-drag;
}
.title-bar-dark .control-button {
color: #ccc;
}
.control-button:hover {
background: rgba(0, 0, 0, 0.05);
}
.title-bar-dark .control-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.close-button:hover {
background: #e81123 !important;
color: #fff !important;
}
.minimize-button:hover,
.maximize-button:hover {
background: rgba(0, 0, 0, 0.08);
}
.title-bar-dark .minimize-button:hover,
.title-bar-dark .maximize-button:hover {
background: rgba(255, 255, 255, 0.15);
}
</style>

View File

@@ -4,6 +4,12 @@ export interface ElectronAPI {
selectFile: (filters?: any[]) => Promise<string[]>
openUrl: (url: string) => Promise<{ success: boolean; error?: string }>
// 窗口控制
windowMinimize: () => Promise<void>
windowMaximize: () => Promise<void>
windowClose: () => Promise<void>
windowIsMaximized: () => Promise<boolean>
// 初始化相关API
checkEnvironment: () => Promise<any>
downloadPython: (mirror?: string) => Promise<any>

View File

@@ -98,31 +98,31 @@
<div v-else class="history-layout">
<!-- 左侧日期列表 -->
<div class="date-sidebar">
<!-- &lt;!&ndash; 数据总览 &ndash;&gt;-->
<!-- <div class="overview-section">-->
<!-- <a-card size="small" title="数据总览" class="overview-card">-->
<!-- <div class="overview-stats">-->
<!-- <a-statistic-->
<!-- title="总公招数"-->
<!-- :value="totalOverview.totalRecruit"-->
<!-- :value-style="{ color: '#1890ff', fontSize: '18px' }"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <UserOutlined />-->
<!-- </template>-->
<!-- </a-statistic>-->
<!-- <a-statistic-->
<!-- title="总掉落数"-->
<!-- :value="totalOverview.totalDrop"-->
<!-- :value-style="{ color: '#52c41a', fontSize: '18px' }"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <GiftOutlined />-->
<!-- </template>-->
<!-- </a-statistic>-->
<!-- </div>-->
<!-- </a-card>-->
<!-- </div>-->
<!-- &lt;!&ndash; 数据总览 &ndash;&gt;-->
<!-- <div class="overview-section">-->
<!-- <a-card size="small" title="数据总览" class="overview-card">-->
<!-- <div class="overview-stats">-->
<!-- <a-statistic-->
<!-- title="总公招数"-->
<!-- :value="totalOverview.totalRecruit"-->
<!-- :value-style="{ color: '#1890ff', fontSize: '18px' }"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <UserOutlined />-->
<!-- </template>-->
<!-- </a-statistic>-->
<!-- <a-statistic-->
<!-- title="总掉落数"-->
<!-- :value="totalOverview.totalDrop"-->
<!-- :value-style="{ color: '#52c41a', fontSize: '18px' }"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <GiftOutlined />-->
<!-- </template>-->
<!-- </a-statistic>-->
<!-- </div>-->
<!-- </a-card>-->
<!-- </div>-->
<!-- 日期折叠列表 -->
<div class="date-list">
@@ -174,7 +174,9 @@
<a-card size="small" title="记录条目" class="records-card">
<template #extra>
<a-space>
<span class="record-count">{{ selectedUserData.index?.length || 0 }} 条记录</span>
<span class="record-count"
>{{ selectedUserData.index?.length || 0 }} 条记录</span
>
<HistoryOutlined />
</a-space>
</template>
@@ -183,18 +185,18 @@
v-for="(record, index) in selectedUserData.index || []"
:key="record.jsonFile"
class="record-item"
:class="{
:class="{
active: selectedRecordIndex === index,
success: record.status === '完成',
error: record.status === '异常'
error: record.status === '异常',
}"
@click="handleSelectRecord(index, record)"
>
<div class="record-info">
<div class="record-header">
<span class="record-time">{{ record.date }}</span>
<a-tag
:color="record.status === '完成' ? 'success' : 'error'"
<a-tag
:color="record.status === '完成' ? 'success' : 'error'"
size="small"
>
{{ record.status }}
@@ -218,7 +220,9 @@
<a-card size="small" class="stat-card">
<template #title>
<span>公招统计</span>
<span v-if="selectedRecordIndex >= 0" class="stat-subtitle">当前记录</span>
<span v-if="selectedRecordIndex >= 0" class="stat-subtitle"
>当前记录</span
>
<span v-else class="stat-subtitle">用户总计</span>
</template>
<template #extra>
@@ -250,7 +254,9 @@
<a-card size="small" class="stat-card">
<template #title>
<span>掉落统计</span>
<span v-if="selectedRecordIndex >= 0" class="stat-subtitle">当前记录</span>
<span v-if="selectedRecordIndex >= 0" class="stat-subtitle"
>当前记录</span
>
<span v-else class="stat-subtitle">用户总计</span>
</template>
<template #extra>
@@ -526,8 +532,6 @@ const handleRefresh = () => {
handleSearch()
}
// 快捷时间选择处理
const handleQuickTimeSelect = (preset: (typeof timePresets)[0]) => {
currentPreset.value = preset.key
@@ -607,7 +611,6 @@ const getDateStatusColor = (users: Record<string, HistoryData>) => {
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding: 0 24px;
}
.header-title h1 {
@@ -618,12 +621,10 @@ const getDateStatusColor = (users: Record<string, HistoryData>) => {
.search-section {
margin-bottom: 24px;
padding: 0 24px;
}
.history-content {
padding: 0 24px;
height: calc(100vh - 200px);
height: calc(80vh - 200px);
}
.empty-state {
@@ -959,16 +960,16 @@ const getDateStatusColor = (users: Record<string, HistoryData>) => {
.history-layout {
flex-direction: column;
}
.date-sidebar {
width: 100%;
max-height: 300px;
}
.detail-content {
flex-direction: column;
}
.log-area {
width: 100%;
max-height: 400px;

View File

@@ -927,7 +927,6 @@ onUnmounted(() => {
<style scoped>
.scheduler-container {
padding: 24px;
height: 100%;
display: flex;
flex-direction: column;