refactor: 增加TitleBar组件,微调页面布局
This commit is contained in:
@@ -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, {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
<!-- <!– 折叠按钮 –>-->
|
||||
<!-- <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);
|
||||
}
|
||||
|
||||
/* 主菜单容器 */
|
||||
|
||||
193
frontend/src/components/TitleBar.vue
Normal file
193
frontend/src/components/TitleBar.vue
Normal 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>
|
||||
6
frontend/src/types/electron.d.ts
vendored
6
frontend/src/types/electron.d.ts
vendored
@@ -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>
|
||||
|
||||
@@ -98,31 +98,31 @@
|
||||
<div v-else class="history-layout">
|
||||
<!-- 左侧日期列表 -->
|
||||
<div class="date-sidebar">
|
||||
<!-- <!– 数据总览 –>-->
|
||||
<!-- <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="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;
|
||||
|
||||
@@ -927,7 +927,6 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.scheduler-container {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user