osCtl.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { BrowserWindow, shell } from 'electron';
  2. import { getMainWindow } from 'ee-core/electron';
  3. import { os } from "../service/os/os";
  4. import { localSettingsService } from "../service/setting/localSettings";
  5. import path from 'node:path';
  6. import fs from 'node:fs';
  7. import { logger } from "ee-core/log";
  8. /** 本机设置里存离线地图目录的键名 */
  9. const MAP_LOCAL_KEY = 'mapLocal';
  10. /**
  11. * 按 IPC 发起方解析要操作的窗口。
  12. * 无边框窗口的控制按钮不带窗口参数:智能工作台里点最小化/关闭,必须操作它自己。
  13. */
  14. function resolveSenderWindow(event) {
  15. const sender = event?.sender;
  16. const win = sender ? BrowserWindow.fromWebContents(sender) : null;
  17. return win && !win.isDestroyed() ? win : getMainWindow();
  18. }
  19. /**
  20. * 窗口控制(用于无边框窗口拖拽)
  21. * @class
  22. */
  23. class OsCtl {
  24. /**
  25. * 最小化窗口
  26. */
  27. async minimizeWindow(params, event) {
  28. await os.minimizeWindow(resolveSenderWindow(event));
  29. }
  30. /**
  31. * 最大化窗口
  32. */
  33. async maximizeWindow(params, event) {
  34. await os.maximizeWindow(resolveSenderWindow(event));
  35. }
  36. /**
  37. * 取消最大化窗口
  38. */
  39. async unmaximizeWindow(params, event) {
  40. await os.unmaximizeWindow(resolveSenderWindow(event));
  41. }
  42. /**
  43. * 关闭窗口
  44. */
  45. async closeWindow(params, event) {
  46. await os.closeWindow(resolveSenderWindow(event));
  47. }
  48. /**
  49. * 是否最大化窗口
  50. */
  51. async isMaximized(params, event) {
  52. return await os.isMaximized(resolveSenderWindow(event));
  53. }
  54. /**
  55. * 切换开发工具
  56. */
  57. async toggleDevTools(params, event) {
  58. await os.toggleDevTools(resolveSenderWindow(event));
  59. }
  60. /**
  61. * 打开用户数据目录
  62. */
  63. async openUserDataFolder() {
  64. await os.openUserDataFolder();
  65. }
  66. /**
  67. * 打开安装目录
  68. */
  69. async openInstallFolder() {
  70. await os.openInstallFolder();
  71. }
  72. /**
  73. * 选择目录(返回目录路径)
  74. */
  75. async selectDirectory(params, event) {
  76. return await os.selectDirectory(resolveSenderWindow(event));
  77. }
  78. /**
  79. * 分块读本机文件(离线地图瓦片用;params: { filePath, offset?, length })。
  80. *
  81. * 方法名沿用改造前前端已调用的 `readBinaryFileRange`;返回项目统一的 {success,data|message} 信封,
  82. * 读取失败(路径非法/文件不存在等)不抛到 Electron 层,调用方看 message 即可。
  83. */
  84. async readBinaryFileRange(params) {
  85. try {
  86. return { success: true, data: await os.readBinaryFileRange(params) };
  87. } catch (e) {
  88. const msg = e instanceof Error ? e.message : String(e);
  89. logger.warn('[osCtl] readBinaryFileRange failed:', msg);
  90. return { success: false, message: msg };
  91. }
  92. }
  93. /**
  94. * 读本机文本文件(离线地图样式用;params: { filePath, encoding? })
  95. */
  96. async readTextFile(params) {
  97. try {
  98. return { success: true, data: { text: await os.readTextFile(params) } };
  99. } catch (e) {
  100. const msg = e instanceof Error ? e.message : String(e);
  101. logger.warn('[osCtl] readTextFile failed:', msg);
  102. return { success: false, message: msg };
  103. }
  104. }
  105. /**
  106. * 读取本机离线地图目录(未设置为空串)。
  107. *
  108. * 存在本机(local-settings.json),不走服务端:地图瓦片在本机时,后端可能部署在别的机器上。
  109. */
  110. async getMapLocalPath() {
  111. return { success: true, data: { path: localSettingsService.get(MAP_LOCAL_KEY) } };
  112. }
  113. /**
  114. * 保存本机离线地图目录;params: { path },path 传空表示清空。
  115. *
  116. * 校验与 openPath 一致:只接受绝对路径、必须已存在、且必须是目录。
  117. */
  118. async setMapLocalPath(params) {
  119. try {
  120. const target = typeof params?.path === 'string' ? params.path.trim() : '';
  121. if (target) {
  122. if (!path.isAbsolute(target)) throw new Error('只支持绝对路径');
  123. if (!fs.existsSync(target)) throw new Error(`目录不存在:${target}`);
  124. if (!fs.statSync(target).isDirectory()) throw new Error('请选择目录,而不是文件');
  125. }
  126. const saved = localSettingsService.set(MAP_LOCAL_KEY, target);
  127. return { success: true, data: { path: saved } };
  128. } catch (e) {
  129. const msg = e instanceof Error ? e.message : String(e);
  130. logger.warn('[osCtl] setMapLocalPath failed:', msg);
  131. return { success: false, message: msg };
  132. }
  133. }
  134. /**
  135. * 获取应用信息
  136. */
  137. async getAppInfo() {
  138. return await os.getAppInfo();
  139. }
  140. /**
  141. * 使用系统默认浏览器打开外部链接
  142. */
  143. async openExternal(url) {
  144. return await os.openExternal(url);
  145. }
  146. /**
  147. * 通过 Node.js 主进程流式上传清鉴数据源文件
  148. * 在 controller 层捕获异常并返回结构化结果,避免异常被 Electron
  149. * 的 ipcMain.handle 包装成 "Error occurred in handler for ..." 前缀
  150. * @param params { dirPath, uploadUrl, token }
  151. * @returns { success: boolean, data?, message? }
  152. */
  153. async uploadQingJianFiles(params) {
  154. try {
  155. const data = await os.uploadQingJianFiles(params);
  156. return { success: true, data };
  157. } catch (e) {
  158. const msg = e instanceof Error ? e.message : String(e);
  159. logger.error('[osCtl] uploadQingJianFiles failed:', msg);
  160. return { success: false, message: msg };
  161. }
  162. }
  163. }
  164. OsCtl.toString = () => "OsCtl";
  165. export default OsCtl;