diff --git a/electron-vue-template/src/main/main.ts b/electron-vue-template/src/main/main.ts index b2b3643..dd3ad17 100644 --- a/electron-vue-template/src/main/main.ts +++ b/electron-vue-template/src/main/main.ts @@ -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); }) \ No newline at end of file diff --git a/electron-vue-template/src/renderer/App.vue b/electron-vue-template/src/renderer/App.vue index 1ec8b54..68d1805 100644 --- a/electron-vue-template/src/renderer/App.vue +++ b/electron-vue-template/src/renderer/App.vue @@ -1,6 +1,6 @@ - + @@ -503,7 +669,7 @@ onMounted(async () => { - + + +/* 浮动版本信息 */ +.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; +} + \ No newline at end of file diff --git a/electron-vue-template/src/renderer/main.ts b/electron-vue-template/src/renderer/main.ts index f922389..e61614a 100644 --- a/electron-vue-template/src/renderer/main.ts +++ b/electron-vue-template/src/renderer/main.ts @@ -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') diff --git a/electron-vue-template/src/renderer/style.css b/electron-vue-template/src/renderer/style.css index 84a0050..466ff6c 100644 --- a/electron-vue-template/src/renderer/style.css +++ b/electron-vue-template/src/renderer/style.css @@ -13,7 +13,6 @@ -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } - a { font-weight: 500; color: #646cff; diff --git a/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java b/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java index 4be22ff..d8af212 100644 --- a/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java +++ b/erp_client_sb/src/main/java/com/tashow/erp/controller/UpdateController.java @@ -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 checkUpdate() { - Map 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 getStoredUpdateInfo() { - Map 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 cancelDownload() { Map 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 saveSkippedVersion(@RequestBody Map request) { - Map 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 saveSkippedVersion(@RequestBody Map request) { +// Map result = new HashMap<>(); +// try { +// saveUpdateInfo("skippedUpdateVersion", request.get("version")); +// result.put("success", true); +// } catch (Exception e) { +// result.put("success", false); +// } +// return result; +// } } \ No newline at end of file