1
This commit is contained in:
@@ -12,187 +12,172 @@ let splashWindow: BrowserWindow | null = null;
|
||||
let appOpened = false;
|
||||
|
||||
function openAppIfNotOpened() {
|
||||
if (appOpened) return;
|
||||
appOpened = true;
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
if (splashWindow) { splashWindow.close(); splashWindow = null; }
|
||||
if (appOpened) return;
|
||||
appOpened = true;
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
if (splashWindow) { splashWindow.close(); splashWindow = null; }
|
||||
}
|
||||
|
||||
// 启动后端 Spring Boot(使用你提供的绝对路径)
|
||||
function startSpringBoot() {
|
||||
const jarPath = 'C:/Users/ZiJIe/Desktop/wox/RuoYi-Vue/ruoyi-admin/target/ruoyi-admin.jar';
|
||||
const jarPath = 'C:/Users/ZiJIe/Desktop/wox/RuoYi-Vue/ruoyi-admin/target/ruoyi-admin.jar';
|
||||
|
||||
springProcess = spawn('java', ['-jar', jarPath], {
|
||||
cwd: dirname(jarPath),
|
||||
detached: false
|
||||
});
|
||||
springProcess = spawn('java', ['-jar', jarPath], {
|
||||
cwd: dirname(jarPath),
|
||||
detached: false
|
||||
});
|
||||
|
||||
// 打印后端日志,监听启动成功标志
|
||||
springProcess.stdout.on('data', (data) => {
|
||||
console.log(`SpringBoot: ${data}`);
|
||||
// 检测到启动成功日志立即进入主界面
|
||||
if (data.toString().includes('Started RuoYiApplication')) {
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
});
|
||||
// 打印后端日志,监听启动成功标志
|
||||
springProcess.stdout.on('data', (data) => {
|
||||
console.log(`SpringBoot: ${data}`);
|
||||
// 检测到启动成功日志立即进入主界面
|
||||
if (data.toString().includes('Started RuoYiApplication')) {
|
||||
openAppIfNotOpened();
|
||||
}
|
||||
});
|
||||
|
||||
// 打印后端错误,检测启动失败
|
||||
springProcess.stderr.on('data', (data) => {
|
||||
console.error(`SpringBoot ERROR: ${data}`);
|
||||
const errorStr = data.toString();
|
||||
// 检测到关键错误信息,直接退出
|
||||
if (errorStr.includes('APPLICATION FAILED TO START') ||
|
||||
errorStr.includes('Port') && errorStr.includes('already in use') ||
|
||||
errorStr.includes('Unable to start embedded Tomcat')) {
|
||||
console.error('后端启动失败,程序即将退出');
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
// 打印后端错误,检测启动失败
|
||||
springProcess.stderr.on('data', (data) => {
|
||||
console.error(`SpringBoot ERROR: ${data}`);
|
||||
const errorStr = data.toString();
|
||||
// 检测到关键错误信息,直接退出
|
||||
if (errorStr.includes('APPLICATION FAILED TO START') ||
|
||||
errorStr.includes('Port') && errorStr.includes('already in use') ||
|
||||
errorStr.includes('Unable to start embedded Tomcat')) {
|
||||
console.error('后端启动失败,程序即将退出');
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// 后端退出时,前端同步退出
|
||||
springProcess.on('close', (code) => {
|
||||
console.log(`SpringBoot exited with code ${code}`);
|
||||
if (mainWindow) {
|
||||
mainWindow.close();
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
// 后端退出时,前端同步退出
|
||||
springProcess.on('close', (code) => {
|
||||
console.log(`SpringBoot exited with code ${code}`);
|
||||
if (mainWindow) {
|
||||
mainWindow.close();
|
||||
} else {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭后端进程(Windows 使用 taskkill 结束整个进程树)
|
||||
function stopSpringBoot() {
|
||||
if (!springProcess) return;
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// Force kill the whole process tree on Windows
|
||||
try {
|
||||
const pid = springProcess.pid;
|
||||
if (pid !== undefined && pid !== null) {
|
||||
spawn('taskkill', ['/pid', String(pid), '/f', '/t']);
|
||||
if (!springProcess) return;
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// Force kill the whole process tree on Windows
|
||||
try {
|
||||
const pid = springProcess.pid;
|
||||
if (pid !== undefined && pid !== null) {
|
||||
spawn('taskkill', ['/pid', String(pid), '/f', '/t']);
|
||||
} else {
|
||||
springProcess.kill();
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
springProcess.kill();
|
||||
}
|
||||
} else {
|
||||
springProcess.kill();
|
||||
springProcess.kill('SIGTERM');
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
springProcess.kill();
|
||||
}
|
||||
} else {
|
||||
springProcess.kill('SIGTERM');
|
||||
} catch (e) {
|
||||
console.error('Failed to stop Spring Boot process:', e);
|
||||
} finally {
|
||||
springProcess = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to stop Spring Boot process:', e);
|
||||
} finally {
|
||||
springProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
function createWindow () {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
icon: join(__dirname, '../renderer/icon/icon.png'), // 添加窗口图标
|
||||
webPreferences: {
|
||||
preload: join(__dirname, 'preload.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
}
|
||||
});
|
||||
|
||||
// 彻底隐藏原生菜单栏
|
||||
try {
|
||||
Menu.setApplicationMenu(null);
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
if (typeof (mainWindow as any).setMenu === 'function') {
|
||||
(mainWindow as any).setMenu(null);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// 生产环境加载本地文件
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const rendererPort = process.argv[2] || 8083;
|
||||
mainWindow.loadURL(`http://localhost:${rendererPort}`);
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
|
||||
}
|
||||
});
|
||||
|
||||
// 彻底隐藏原生菜单栏
|
||||
try {
|
||||
Menu.setApplicationMenu(null);
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
if (typeof (mainWindow as any).setMenu === 'function') {
|
||||
(mainWindow as any).setMenu(null);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const rendererPort = process.argv[2];
|
||||
mainWindow.loadURL(`http://localhost:${rendererPort}`);
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// 预创建主窗口(隐藏)
|
||||
createWindow();
|
||||
// 预创建主窗口(隐藏)
|
||||
createWindow();
|
||||
|
||||
// 显示启动页
|
||||
const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize;
|
||||
const splashW = Math.min(Math.floor(sw * 0.8), 1800);
|
||||
const splashH = Math.min(Math.floor(sh * 0.8), 1200);
|
||||
splashWindow = new BrowserWindow({
|
||||
width: splashW,
|
||||
height: splashH,
|
||||
frame: false,
|
||||
transparent: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
show: true,
|
||||
center: true,
|
||||
});
|
||||
// 显示启动页
|
||||
const { width: sw, height: sh } = screen.getPrimaryDisplay().workAreaSize;
|
||||
const splashW = Math.min(Math.floor(sw * 0.8), 1800);
|
||||
const splashH = Math.min(Math.floor(sh * 0.8), 1200);
|
||||
splashWindow = new BrowserWindow({
|
||||
width: splashW,
|
||||
height: splashH,
|
||||
frame: false,
|
||||
transparent: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
show: true,
|
||||
center: true,
|
||||
});
|
||||
|
||||
const candidateSplashPaths = [
|
||||
join(__dirname, '../../public', 'splash.html'),
|
||||
];
|
||||
const foundSplash = candidateSplashPaths.find(p => existsSync(p));
|
||||
if (foundSplash) {
|
||||
splashWindow.loadFile(foundSplash);
|
||||
}
|
||||
|
||||
// 注释掉后端启动,便于快速调试前端
|
||||
// startSpringBoot();
|
||||
|
||||
// 快速调试模式 - 直接打开主窗口
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 1000);
|
||||
|
||||
// 注释掉超时机制
|
||||
/*
|
||||
setTimeout(() => {
|
||||
if (!appOpened) {
|
||||
console.error('后端启动超时,程序即将退出');
|
||||
app.quit();
|
||||
const candidateSplashPaths = [
|
||||
join(__dirname, '../../public', 'splash.html'),
|
||||
];
|
||||
const foundSplash = candidateSplashPaths.find(p => existsSync(p));
|
||||
if (foundSplash) {
|
||||
splashWindow.loadFile(foundSplash);
|
||||
}
|
||||
}, 30000);
|
||||
*/
|
||||
|
||||
// 保守 CSP(仅允许自身脚本),避免引入不必要的外部脚本
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': ['script-src \'self\'']
|
||||
}
|
||||
})
|
||||
})
|
||||
// 注释掉后端启动,便于快速调试前端
|
||||
// startSpringBoot();
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
// 快速调试模式 - 直接打开主窗口
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 1000);
|
||||
|
||||
|
||||
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
stopSpringBoot();
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
stopSpringBoot();
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
stopSpringBoot();
|
||||
stopSpringBoot();
|
||||
});
|
||||
|
||||
ipcMain.on('message', (event, message) => {
|
||||
console.log(message);
|
||||
console.log(message);
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref, computed, defineAsyncComponent, type Component} from 'vue'
|
||||
import {ElConfigProvider, ElMessage, ElMessageBox} from 'element-plus'
|
||||
import {onMounted, ref, computed, defineAsyncComponent, type Component, onUnmounted} from 'vue'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
// 图标已移至对应组件
|
||||
import 'element-plus/dist/index.css'
|
||||
@@ -11,6 +11,7 @@ import {deviceApi, type DeviceItem, type DeviceQuota} from './api/device'
|
||||
const LoginDialog = defineAsyncComponent(() => import('./components/auth/LoginDialog.vue'))
|
||||
const RegisterDialog = defineAsyncComponent(() => import('./components/auth/RegisterDialog.vue'))
|
||||
const NavigationBar = defineAsyncComponent(() => import('./components/layout/NavigationBar.vue'))
|
||||
// const UpdateDialog = defineAsyncComponent(() => import('./components/common/UpdateDialog.vue'))
|
||||
const RakutenDashboard = defineAsyncComponent(() => import('./components/rakuten/RakutenDashboard.vue'))
|
||||
const AmazonDashboard = defineAsyncComponent(() => import('./components/amazon/AmazonDashboard.vue'))
|
||||
const ZebraDashboard = defineAsyncComponent(() => import('./components/zebra/ZebraDashboard.vue'))
|
||||
@@ -47,6 +48,10 @@ const devices = ref<DeviceItem[]>([])
|
||||
const deviceQuota = ref<DeviceQuota>({limit: 0, used: 0})
|
||||
const userPermissions = ref<string>('')
|
||||
|
||||
// 更新状态
|
||||
const updateStatus = ref<any>(null)
|
||||
const showUpdateNotification = ref(false)
|
||||
|
||||
// 菜单配置 - 复刻ERP客户端格式
|
||||
const menuConfig = [
|
||||
{key: 'rakuten', name: 'Rakuten', index: 'rakuten', icon: 'R'},
|
||||
@@ -150,11 +155,20 @@ async function handleLoginSuccess(data: { token: string; permissions?: string })
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
|
||||
const token = await authApi.getToken()
|
||||
if (token) {
|
||||
await authApi.logout(token)
|
||||
// 主动设置设备离线
|
||||
try {
|
||||
const deviceId = await getClientIdFromToken()
|
||||
if (deviceId) {
|
||||
await deviceApi.offline({ deviceId })
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
|
||||
const token = await authApi.getToken()
|
||||
if (token) {
|
||||
await authApi.logout(token)
|
||||
}
|
||||
|
||||
await authApi.deleteTokenCache()
|
||||
// 清理前端状态
|
||||
@@ -164,8 +178,7 @@ async function logout() {
|
||||
showAuthDialog.value = true
|
||||
showDeviceDialog.value = false
|
||||
|
||||
// 关闭SSE连接`-+++++++
|
||||
|
||||
// 关闭SSE连接
|
||||
SSEManager.disconnect()
|
||||
}
|
||||
|
||||
@@ -181,7 +194,10 @@ async function handleUserClick() {
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
await logout()
|
||||
ElMessage.success('已退出登录')
|
||||
ElMessage({
|
||||
message: '已退出登录',
|
||||
type: 'success'
|
||||
})
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
@@ -285,8 +301,12 @@ const SSEManager = {
|
||||
|
||||
handleMessage(e: MessageEvent) {
|
||||
try {
|
||||
console.log('SSE消息:', e.data)
|
||||
// 处理ping心跳
|
||||
if (e.type === 'ping') {
|
||||
return // ping消息自动保持连接,无需处理
|
||||
}
|
||||
|
||||
console.log('SSE消息:', e.data)
|
||||
const payload = JSON.parse(e.data)
|
||||
switch (payload.type) {
|
||||
case 'ready':
|
||||
@@ -294,11 +314,17 @@ const SSEManager = {
|
||||
break
|
||||
case 'DEVICE_REMOVED':
|
||||
logout()
|
||||
ElMessage.warning('您的设备已被移除,请重新登录')
|
||||
ElMessage({
|
||||
message: '您的设备已被移除,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
break
|
||||
case 'FORCE_LOGOUT':
|
||||
logout()
|
||||
ElMessage.warning('会话已失效,请重新登录')
|
||||
ElMessage({
|
||||
message: '会话已失效,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
break
|
||||
case 'PERMISSIONS_UPDATED':
|
||||
checkAuth()
|
||||
@@ -336,7 +362,10 @@ async function openDeviceManager() {
|
||||
|
||||
async function fetchDeviceData() {
|
||||
if (!currentUsername.value) {
|
||||
ElMessage.warning('未获取到用户名,请重新登录')
|
||||
ElMessage({
|
||||
message: '未获取到用户名,请重新登录',
|
||||
type: 'warning'
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -349,7 +378,10 @@ async function fetchDeviceData() {
|
||||
const clientId = await getClientIdFromToken()
|
||||
devices.value = (list || []).map(d => ({...d, isCurrent: d.deviceId === clientId})) as any
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '获取设备列表失败')
|
||||
ElMessage({
|
||||
message: e?.message || '获取设备列表失败',
|
||||
type: 'error'
|
||||
})
|
||||
} finally {
|
||||
deviceLoading.value = false
|
||||
}
|
||||
@@ -373,9 +405,130 @@ async function confirmRemoveDevice(row: DeviceItem & { isCurrent?: boolean }) {
|
||||
await logout()
|
||||
}
|
||||
|
||||
ElMessage.success('已移除设备')
|
||||
ElMessage({
|
||||
message: '已移除设备',
|
||||
type: 'success'
|
||||
})
|
||||
} catch (e: any) {
|
||||
ElMessage.error('移除设备失败: ' + ((e as any)?.message || '未知错误'))
|
||||
ElMessage({
|
||||
message: '移除设备失败: ' + ((e as any)?.message || '未知错误'),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新相关功能
|
||||
function setupUpdateHandler() {
|
||||
if (!(window as any).electronAPI) return
|
||||
|
||||
(window as any).electronAPI.onUpdateStatus((status: any) => {
|
||||
updateStatus.value = status
|
||||
handleUpdateStatus(status)
|
||||
})
|
||||
}
|
||||
|
||||
function handleUpdateStatus(status: any) {
|
||||
switch (status.type) {
|
||||
case 'checking':
|
||||
console.log('正在检查更新...')
|
||||
break
|
||||
case 'available':
|
||||
showUpdateAvailableNotification(status)
|
||||
break
|
||||
case 'not-available':
|
||||
console.log('当前已是最新版本')
|
||||
break
|
||||
case 'error':
|
||||
ElMessage({
|
||||
message: `更新检查失败: ${status.error}`,
|
||||
type: 'error'
|
||||
})
|
||||
break
|
||||
case 'download-progress':
|
||||
// 可以在这里显示下载进度
|
||||
console.log(`下载进度: ${status.progress}%`)
|
||||
break
|
||||
case 'downloaded':
|
||||
showUpdateReadyNotification()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function showUpdateAvailableNotification(status: any) {
|
||||
ElMessage({
|
||||
message: `发现新版本 ${status.version},是否立即下载?`,
|
||||
type: 'info'
|
||||
})
|
||||
// 可以在这里添加确认对话框
|
||||
ElMessageBox.confirm(`新版本 ${status.version} 已发布,是否立即下载?`, '发现新版本', {
|
||||
confirmButtonText: '立即下载',
|
||||
cancelButtonText: '稍后提醒',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
downloadUpdate()
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
function showUpdateReadyNotification() {
|
||||
ElMessageBox.confirm('更新已下载完成,是否重启应用以安装更新?', '更新准备就绪', {
|
||||
confirmButtonText: '立即重启',
|
||||
cancelButtonText: '稍后重启',
|
||||
type: 'success'
|
||||
}).then(() => {
|
||||
installUpdate()
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
async function checkForUpdates() {
|
||||
if (!(window as any).electronAPI) return
|
||||
|
||||
try {
|
||||
const result = await (window as any).electronAPI.checkForUpdates()
|
||||
if (!result.success) {
|
||||
console.log('更新检查:', result.message || result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadUpdate() {
|
||||
if (!(window as any).electronAPI) return
|
||||
|
||||
try {
|
||||
ElMessage({
|
||||
message: '开始下载更新...',
|
||||
type: 'info'
|
||||
})
|
||||
const result = await (window as any).electronAPI.downloadUpdate()
|
||||
if (!result.success) {
|
||||
ElMessage({
|
||||
message: `下载失败: ${result.error}`,
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage({
|
||||
message: '下载更新失败',
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function installUpdate() {
|
||||
if (!(window as any).electronAPI) return
|
||||
|
||||
try {
|
||||
(window as any).electronAPI.installUpdate()
|
||||
} catch (error) {
|
||||
ElMessage({
|
||||
message: '安装更新失败',
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,11 +536,24 @@ onMounted(async () => {
|
||||
showContent()
|
||||
await checkAuth()
|
||||
|
||||
// 设置更新处理器
|
||||
setupUpdateHandler()
|
||||
|
||||
// 监听页面关闭,断开SSE连接(会自动设置离线)
|
||||
window.addEventListener('beforeunload', () => {
|
||||
SSEManager.disconnect()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if ((window as any).electronAPI) {
|
||||
(window as any).electronAPI.removeUpdateStatusListener()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider :locale="zhCnLocale">
|
||||
<div>
|
||||
<div id="app-root" class="root">
|
||||
<div class="loading-container" id="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
@@ -503,7 +669,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -599,7 +765,7 @@ onMounted(async () => {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin: 8px 6px 10px;
|
||||
text-align: left; /* “电商平台”四个字靠左 */
|
||||
text-align: left; /* "电商平台"四个字靠左 */
|
||||
}
|
||||
|
||||
.menu {
|
||||
@@ -758,4 +924,19 @@ onMounted(async () => {
|
||||
}
|
||||
.device-count { color: #909399; font-weight: 500; }
|
||||
.device-subtitle { font-size: 12px; color: #909399; }
|
||||
</style>
|
||||
|
||||
/* 浮动版本信息 */
|
||||
.version-info {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
background: rgba(255,255,255,0.9);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
z-index: 1000;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css';
|
||||
import 'element-plus/dist/index.css'
|
||||
import './style.css';
|
||||
import ElementPlus from 'element-plus'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
package com.tashow.erp.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.entity.UpdateStatusEntity;
|
||||
import com.tashow.erp.repository.UpdateStatusRepository;
|
||||
import com.tashow.erp.service.IAuthService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -31,12 +24,7 @@ public class UpdateController {
|
||||
private String currentVersion;
|
||||
@Value("${project.build.time:}")
|
||||
private String buildTime;
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
@Autowired
|
||||
private IAuthService authService;
|
||||
|
||||
@Autowired
|
||||
private UpdateStatusRepository updateStatusRepository;
|
||||
// 简化:移除与鉴权/版本转发/SQLite 存储相关的依赖
|
||||
|
||||
// 下载进度跟踪
|
||||
private volatile int downloadProgress = 0;
|
||||
@@ -61,137 +49,9 @@ public class UpdateController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查版本更新
|
||||
*
|
||||
* @return 版本更新信息
|
||||
*/
|
||||
@GetMapping("/check")
|
||||
public Map<String, Object> checkUpdate() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String response = authService.checkVersion(currentVersion);
|
||||
// 解析JSON响应
|
||||
JsonNode responseNode = objectMapper.readTree(response);
|
||||
|
||||
result.put("success", true);
|
||||
result.put("currentVersion", currentVersion);
|
||||
|
||||
// 检查响应格式并提取数据
|
||||
JsonNode dataNode = null;
|
||||
dataNode = responseNode.get("data");
|
||||
|
||||
// 直接使用服务器返回的needUpdate字段
|
||||
boolean needUpdate = dataNode.has("needUpdate") &&
|
||||
dataNode.get("needUpdate").asBoolean();
|
||||
// 添加跳过版本信息,让前端自己判断
|
||||
String skippedVersion = updateStatusRepository.findByKeyName("skippedUpdateVersion")
|
||||
.map(entity -> entity.getValueData()).orElse(null);
|
||||
result.put("skippedVersion", skippedVersion);
|
||||
|
||||
result.put("needUpdate", needUpdate);
|
||||
|
||||
// 获取最新版本号
|
||||
if (dataNode.has("latestVersion")) {
|
||||
result.put("latestVersion", dataNode.get("latestVersion").asText());
|
||||
}
|
||||
|
||||
if (needUpdate) {
|
||||
if (dataNode.has("downloadUrl")) {
|
||||
String downloadUrl = dataNode.get("downloadUrl").asText();
|
||||
result.put("downloadUrl", downloadUrl);
|
||||
saveUpdateInfo("downloadUrl", downloadUrl);
|
||||
}
|
||||
if (dataNode.has("updateTime")) {
|
||||
// 转换时间戳为可读格式
|
||||
long updateTime = dataNode.get("updateTime").asLong();
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String releaseDate = sdf.format(new java.util.Date(updateTime));
|
||||
result.put("releaseDate", releaseDate);
|
||||
saveUpdateInfo("releaseDate", releaseDate);
|
||||
}
|
||||
if (dataNode.has("updateNotes")) {
|
||||
String updateNotes = dataNode.get("updateNotes").asText();
|
||||
result.put("updateNotes", updateNotes);
|
||||
saveUpdateInfo("updateNotes", updateNotes);
|
||||
}
|
||||
if (dataNode.has("fileSize")) {
|
||||
String fileSize = dataNode.get("fileSize").asText();
|
||||
result.put("fileSize", fileSize);
|
||||
saveUpdateInfo("fileSize", fileSize);
|
||||
}
|
||||
if (dataNode.has("latestVersion")) {
|
||||
String latestVersion = dataNode.get("latestVersion").asText();
|
||||
saveUpdateInfo("latestVersion", latestVersion);
|
||||
}
|
||||
} else {
|
||||
// 如果不需要更新,清理之前保存的更新信息
|
||||
clearUpdateInfo();
|
||||
}
|
||||
|
||||
// 从SQLite读取之前保存的更新信息
|
||||
result.putAll(getStoredUpdateInfo());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "版本检查失败:" + e.getMessage());
|
||||
authService.reportError("UPDATE_CHECK_ERROR", "Web版本检查失败", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存更新信息到SQLite
|
||||
*/
|
||||
private void saveUpdateInfo(String key, String value) {
|
||||
try {
|
||||
UpdateStatusEntity entity = updateStatusRepository.findByKeyName(key)
|
||||
.orElse(new UpdateStatusEntity());
|
||||
entity.setKeyName(key);
|
||||
entity.setValueData(value);
|
||||
updateStatusRepository.save(entity);
|
||||
} catch (Exception e) {
|
||||
System.err.println("保存更新信息失败: " + key + " = " + value + ", 错误: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从SQLite获取存储的更新信息
|
||||
*/
|
||||
private Map<String, Object> getStoredUpdateInfo() {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
try {
|
||||
String[] keys = {"downloadUrl", "releaseDate", "updateNotes", "fileSize", "latestVersion"};
|
||||
for (String key : keys) {
|
||||
updateStatusRepository.findByKeyName(key).ifPresent(entity ->
|
||||
info.put(key, entity.getValueData())
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("读取更新信息失败: " + e.getMessage());
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理更新信息
|
||||
*/
|
||||
private void clearUpdateInfo() {
|
||||
try {
|
||||
String[] keys = {"downloadUrl", "releaseDate", "updateNotes", "fileSize", "latestVersion"};
|
||||
for (String key : keys) {
|
||||
updateStatusRepository.findByKeyName(key).ifPresent(entity ->
|
||||
updateStatusRepository.delete(entity)
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("清理更新信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取下载进度
|
||||
*
|
||||
@@ -243,7 +103,6 @@ public class UpdateController {
|
||||
@PostMapping("/cancel")
|
||||
public Map<String, Object> cancelDownload() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
if ("downloading".equals(downloadStatus)) {
|
||||
downloadCancelled = true;
|
||||
downloadStatus = "cancelled";
|
||||
@@ -251,10 +110,9 @@ public class UpdateController {
|
||||
result.put("success", true);
|
||||
result.put("message", "下载已取消");
|
||||
} else if ("completed".equals(downloadStatus)) {
|
||||
// 下载完成时点击取消,不删除文件,只是标记为稍后更新
|
||||
// 下载完成后“稍后更新”:仅关闭弹窗,不触发安装,不改变文件
|
||||
result.put("success", true);
|
||||
result.put("message", "已设置为稍后更新,文件保留");
|
||||
System.out.println("用户选择稍后更新,文件路径: " + tempUpdateFilePath);
|
||||
result.put("message", "已设置为稍后更新");
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "无效的操作状态");
|
||||
@@ -388,6 +246,7 @@ public class UpdateController {
|
||||
result.put("downloadUrl", downloadUrl);
|
||||
String finalDownloadUrl = downloadUrl;
|
||||
new Thread(() -> {
|
||||
|
||||
performUpdate(finalDownloadUrl);
|
||||
}).start();
|
||||
|
||||
@@ -585,19 +444,19 @@ public class UpdateController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存跳过的版本
|
||||
*/
|
||||
@PostMapping("/skip-version")
|
||||
public Map<String, Object> saveSkippedVersion(@RequestBody Map<String, String> request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
saveUpdateInfo("skippedUpdateVersion", request.get("version"));
|
||||
result.put("success", true);
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// /**
|
||||
// * 保存跳过的版本
|
||||
// */
|
||||
// @PostMapping("/skip-version")
|
||||
// public Map<String, Object> saveSkippedVersion(@RequestBody Map<String, String> request) {
|
||||
// Map<String, Object> result = new HashMap<>();
|
||||
// try {
|
||||
// saveUpdateInfo("skippedUpdateVersion", request.get("version"));
|
||||
// result.put("success", true);
|
||||
// } catch (Exception e) {
|
||||
// result.put("success", false);
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user