This commit is contained in:
张成
2025-12-22 16:26:59 +08:00
parent aa2d03ee30
commit e17d5610f5
54 changed files with 11735 additions and 3 deletions

278
app/App.vue Normal file
View File

@@ -0,0 +1,278 @@
<template>
<div class="container">
<header class="header">
<div class="header-left">
<h1>Boss - 远程监听服务 <span style="font-size: 0.7em; opacity: 0.8;">v{{ currentVersion }}</span></h1>
<div class="status-indicator">
<span :class="statusDotClass"></span>
<span>{{ isConnected ? '已连接' : '未连接' }}</span>
</div>
</div>
<div class="header-right">
<!-- 登录页面不显示 UserMenu -->
<UserMenu v-if="showSidebar" />
</div>
</header>
<div class="main-content">
<!-- 左侧菜单 - 登录页面不显示 -->
<Sidebar v-if="showSidebar" />
<!-- 内容区域 - 使用 router-view 切换页面 -->
<div class="content-area" :class="{ 'full-width': !showSidebar }">
<router-view />
</div>
</div>
<!-- 更新弹窗 - 使用 store 管理状态 -->
<UpdateDialog />
</div>
</template>
<script>
import Sidebar from './components/Sidebar.vue';
import UpdateDialog from './components/UpdateDialog.vue';
import UserMenu from './components/UserMenu.vue';
// 导入 Vuex helpers
import { mapState } from 'vuex';
// 导入 Mixins
import logMixin from './mixins/logMixin.js';
import authMixin from './mixins/authMixin.js';
import mqttMixin from './mixins/mqttMixin.js';
import taskMixin from './mixins/taskMixin.js';
import systemInfoMixin from './mixins/systemInfoMixin.js';
import platformMixin from './mixins/platformMixin.js';
import qrCodeMixin from './mixins/qrCodeMixin.js';
import updateMixin from './mixins/updateMixin.js';
import eventListenerMixin from './mixins/eventListenerMixin.js';
// Vue 应用主组件逻辑
export default {
name: 'App',
mixins: [
logMixin,
authMixin,
mqttMixin,
taskMixin,
systemInfoMixin,
platformMixin,
qrCodeMixin,
updateMixin,
eventListenerMixin
],
components: {
Sidebar,
UpdateDialog,
UserMenu
},
data() {
return {
// 应用启动时间(用于计算运行时间)
startTime: Date.now(),
// 应用加载状态
isLoading: true,
// 浏览器窗口状态
browserWindowVisible: false
};
},
mounted() {
this.init();
},
// beforeDestroy 已在 taskMixin 中定义
watch: {
// 监听登录状态变化
isLoggedIn(newVal, oldVal) {
// 未登录时自动跳转到登录页面
if (!newVal && this.$route.name !== 'Login') {
this.$router.push('/login');
}
// 登录状态从 false 变为 true 时,检查 MQTT 状态(连接由主进程自动处理)
if (newVal && !oldVal) {
// 延迟一下,确保 store 中的 snCode 已经更新
this.$nextTick(() => {
setTimeout(() => {
// MQTT 连接由主进程自动处理,这里只检查状态
if (this.checkMQTTStatus) {
this.checkMQTTStatus();
}
// 开始获取任务状态
if (this.startTaskStatusUpdate) {
this.startTaskStatusUpdate();
}
}, 500);
});
}
}
},
methods: {
async init() {
try {
// 先隐藏加载屏幕,避免一直显示
this.hideLoadingScreen();
if (window.electronAPI && window.electronAPI.invoke) {
const result = await window.electronAPI.invoke('system:get-version');
if (result && result.success && result.version) {
this.currentVersion = result.version;
this.addLog('info', `当前版本: v${this.currentVersion}`);
} else if (window.appInfo && window.appInfo.version) {
this.currentVersion = window.appInfo.version;
}
} else if (window.appInfo && window.appInfo.version) {
this.currentVersion = window.appInfo.version;
}
} catch (error) {
console.error('获取版本号失败:', error);
if (window.appInfo && window.appInfo.version) {
this.$store.commit('app/SET_VERSION', window.appInfo.version);
}
}
this.setupEventListeners();
await this.loadSavedConfig();
// 加载投递配置到 store
if (this.$store && this.$store.dispatch) {
await this.$store.dispatch('delivery/loadDeliveryConfig');
}
this.startSystemInfoUpdate();
// 尝试自动登录
const autoLoginSuccess = await this.tryAutoLogin();
// 如果已登录,开始获取任务状态
if (this.$store.state.auth.isLoggedIn) {
this.startTaskStatusUpdate();
// 检查 MQTT 连接状态
this.checkMQTTStatus();
} else if (!autoLoginSuccess) {
// 未登录时,自动跳转到登录页面
this.$router.push('/login');
}
},
hideLoadingScreen() {
setTimeout(() => {
const loadingScreen = document.getElementById('loading-screen');
const app = document.getElementById('app');
if (loadingScreen) {
loadingScreen.classList.add('hidden');
setTimeout(() => {
if (loadingScreen.parentNode) {
loadingScreen.remove();
}
}, 500);
}
if (app) {
app.style.display = 'block';
}
this.isLoading = false;
}, 500);
},
// setupEventListeners 已在 eventListenerMixin 中定义
// loadSavedConfig, checkActivationStatus, userLogin, tryAutoLogin 已在 authMixin 中定义
// startTaskStatusUpdate, updateCurrentTask, onTaskStatusUpdate 已在 taskMixin 中定义
// connectMQTT, disconnectMQTT, onMQTTConnected, onMQTTDisconnected, onMQTTMessage, onMQTTStatusChange 已在 mqttMixin 中定义
// handleMenuChange, handleUpdateDeliveryConfig, handleLoginFromPage, handleLoginFromDialog 已不需要,使用路由和组件内部处理
/**
* 检查 MQTT 连接状态,如果未连接则尝试重新连接
*/
async checkMQTTStatus() {
try {
if (!window.electronAPI || !window.electronAPI.invoke) {
console.warn('[App] electronAPI 不可用');
return;
}
// 获取当前 MQTT 状态
const status = await window.electronAPI.invoke('mqtt:status');
console.log('[App] MQTT 状态查询结果:', status);
if (status && status.isConnected) {
// 如果已经连接,更新状态
if (this.$store) {
this.$store.dispatch('mqtt/setConnected', true);
console.log('[App] MQTT 状态已更新为已连接');
}
} else {
// 如果未连接,尝试重新连接(需要 snCode
const snCode = this.$store?.state?.auth?.snCode;
if (snCode) {
console.log('[App] MQTT 未连接,尝试重新连接...');
try {
await window.electronAPI.invoke('mqtt:connect', snCode);
console.log('[App] MQTT 重新连接成功');
if (this.$store) {
this.$store.dispatch('mqtt/setConnected', true);
}
} catch (error) {
console.warn('[App] MQTT 重新连接失败:', error);
if (this.$store) {
this.$store.dispatch('mqtt/setConnected', false);
}
}
} else {
console.warn('[App] 无法重新连接 MQTT: 缺少 snCode');
}
}
} catch (error) {
console.error('[App] 检查 MQTT 状态失败:', error);
}
}
},
computed: {
...mapState('app', ['currentVersion']),
...mapState('mqtt', ['isConnected']),
...mapState('auth', ['isLoggedIn', 'userName', 'snCode', 'deviceId', 'remainingDays', 'phone']),
// 根据路由 meta 决定是否显示侧边栏,默认为 true
showSidebar() {
return this.$route.meta.showSidebar !== false;
},
statusDotClass() {
return {
'status-dot': true,
'connected': this.isConnected
};
}
// logEntries 已在 logMixin 中定义
}
// snCode watch 已在 authMixin 中定义
};
</script>
<style lang="less" scoped>
// 登录页面时内容区域不需要背景和padding
.content-area.full-width {
// 当侧边栏隐藏时,登录页面应该有特殊样式
&.login-page {
padding: 0;
background: transparent;
border-radius: 0;
box-shadow: none;
}
}
</style>