Ver código fonte

feat(electron): 集成外部工具启动功能

- 新增 runExternalTool IPC 方法用于启动外部可执行文件
- 在构建配置中添加外部工具箱文件分发规则
- 实现前端调用逻辑及 UI 点击事件处理
- 支持白底图导出与产品图册生成两个外部工具入口
- 添加子进程管理确保单一实例运行并正确传递参数
- 更新样式以区分可点击的外部工具标签项
panqiuyao 1 semana atrás
pai
commit
d38875fa05

+ 6 - 0
electron/config/builder.json

@@ -20,6 +20,12 @@
     "from": "build/extraResources/",
     "to": "extraResources"
   },
+  "extraFiles": [
+    {
+      "from": "build/extraResources/智慧映拍照机辅助工具箱.exe",
+      "to": "."
+    }
+  ],
   "nsis": {
     "oneClick": false,
     "allowElevation": true,

+ 97 - 0
electron/controller/utils.js

@@ -8,6 +8,7 @@ const fs = require('fs');
 const path = require('path');
 const CoreWindow = require('ee-core/electron/window');
 const { BrowserWindow, Menu,app } = require('electron');
+const { spawn } = require('child_process');
 
 const { readConfigFile } = require('../utils/config');
 const configDeault = readConfigFile();
@@ -28,6 +29,102 @@ class UtilsController extends Controller {
     super(ctx);
   }
 
+  /**
+   * 运行外部工具(如exe)
+   * @param {{exeName?: string, exePath?: string, args?: string[]}} params
+   */
+  async runExternalTool (params = {}) {
+    try {
+      const { exeName, exePath, args = [] } = params;
+      const targetName = exePath || exeName;
+
+      if (!targetName) {
+        throw new Error('缺少可执行文件名称');
+      }
+
+      const isPackaged = app.isPackaged;
+      const execDir = isPackaged ? path.dirname(app.getPath('exe')) : path.join(app.getAppPath(), '.');
+      const resourcesDir = process.resourcesPath;
+      const candidates = [];
+
+      const pushCandidate = (candidatePath, isAbsolute = false) => {
+        if (!candidatePath) return;
+        const normalized = isAbsolute || path.isAbsolute(candidatePath)
+          ? candidatePath
+          : path.join(execDir, candidatePath);
+        if (!candidates.includes(normalized)) {
+          candidates.push(normalized);
+        }
+      };
+
+      // 允许前端显式传入候选路径数组
+      if (Array.isArray(params.candidatePaths)) {
+        params.candidatePaths.forEach((p) => pushCandidate(p));
+      }
+
+      if (path.isAbsolute(targetName)) {
+        pushCandidate(targetName, true);
+      } else {
+        if (isPackaged) {
+          pushCandidate(path.join(execDir, targetName), true);
+          pushCandidate(path.join(resourcesDir, targetName), true);
+          pushCandidate(path.join(resourcesDir, 'extraResources', targetName), true);
+        } else {
+          pushCandidate(targetName);
+          pushCandidate(path.join('build', 'extraResources', targetName));
+          pushCandidate(path.join('extraResources', targetName));
+        }
+      }
+
+      console.log('=======');
+      console.log(candidates);
+      const resolvedPath = candidates.find((filePath) => filePath && fs.existsSync(filePath));
+
+      if (!resolvedPath) {
+        throw new Error(`未找到工具:${targetName}`);
+      }
+
+      // 如果已经有正在运行的子进程,则先关闭旧进程,再重新启动新的(保证始终只有一个进程,同时刷新页面参数)
+      if (this.app.externalToolProcess && !this.app.externalToolProcess.killed) {
+        try {
+          this.app.externalToolProcess.kill();
+        } catch (e) {
+          console.warn('关闭旧 externalTool 进程失败(忽略继续启动新进程):', e);
+        }
+        this.app.externalToolProcess = null;
+      }
+
+      const child = spawn(resolvedPath, args, {
+        cwd: path.dirname(resolvedPath),
+        detached: true,
+        stdio: 'ignore',
+        windowsHide: false
+      });
+
+      // 记录子进程,方便后续复用/判断是否已退出
+      this.app.externalToolProcess = child;
+      child.on('exit', () => {
+        if (this.app.externalToolProcess === child) {
+          this.app.externalToolProcess = null;
+        }
+      });
+      child.unref();
+      return {
+        code: 0,
+        data: {
+          path: resolvedPath,
+          reused: false
+        }
+      };
+    } catch (error) {
+      console.error('runExternalTool error:', error);
+      return {
+        code: 1,
+        msg: error.message || '运行外部工具失败'
+      };
+    }
+  }
+
 
   /**
    * 所有方法接收两个参数

+ 1 - 0
frontend/src/utils/ipc.ts

@@ -21,6 +21,7 @@ const icpList = {
         openImage:"controller.utils.openImage",
         getAppConfig:"controller.utils.getAppConfig",
         openFile:"controller.utils.openFile",
+        runExternalTool:"controller.utils.runExternalTool",
         closeAllWindows: 'controller.utils.closeAllWindows'
     },
     setting:{

+ 51 - 3
frontend/src/views/Photography/detail.vue

@@ -84,7 +84,6 @@
 
         <!-- 未开发的功能 -->
         <div class="service-tab disabled" title="功能开发中">
-
           <div class="tab-content">
             <div class="tab-img flex">
               <img src="@/assets/images/detail/xqmb.svg" alt="详情页模板自定义" class="tab-icon" />
@@ -93,7 +92,12 @@
           </div>
         </div>
 
-        <div class="service-tab disabled" title="功能开发中">
+        <div
+          class="service-tab external-tool"
+          title="白底图批量导出"
+          @click="handleWhiteBgExportClick"
+          v-log="{ describe: { action: '点击白底图批量导出', service: '白底图批量导出' } }"
+        >
           <div class="tab-content">
             <div class="tab-img flex">
               <img src="@/assets/images/detail/bdt.svg" alt="白底图批量导出" class="tab-icon" />
@@ -102,7 +106,12 @@
           </div>
         </div>
 
-        <div class="service-tab disabled" title="功能开发中">
+        <div
+          class="service-tab external-tool"
+          title="产品图册生成"
+          @click="handleProductAlbumClick"
+          v-log="{ describe: { action: '点击产品图册生成', service: '产品图册生成' } }"
+        >
           <div class="tab-content">
             <div class="tab-img flex">
               <img src="@/assets/images/detail/cptc.svg" alt="产品图册生成" class="tab-icon" />
@@ -501,6 +510,39 @@ import configInfo from "@/stores/modules/config";
 
 const useConfigInfoStore = configInfo();
 
+const EXTERNAL_TOOL_EXECUTABLE = '智慧映拍照机辅助工具箱.exe'
+
+const launchExternalTool = async (pagePath: string, successMessage: string) => {
+  if (!clientStore?.ipc || !clientStore.isClient) {
+    ElMessage.error('当前环境暂不支持外部工具');
+    return;
+  }
+  if (!pagePath) return;
+
+  const tokenStore = tokenInfo();
+  const token = tokenStore?.getToken || '';
+  const env = (useConfigInfoStore?.appConfig as any)?.env || 'prod';
+  const argString = `token=${token}&page=${pagePath}&env=${env}`;
+
+  try {
+    const result = await clientStore.ipc.invoke(icpList.utils.runExternalTool, {
+      exeName: EXTERNAL_TOOL_EXECUTABLE,
+      args: [argString]
+    });
+    if (result?.code === 0) {
+      ElMessage.success(successMessage);
+    } else {
+      throw new Error(result?.msg || '启动外部工具失败');
+    }
+  } catch (error: any) {
+    console.error('launchExternalTool error:', error);
+    ElMessage.error(error?.message || '打开外部工具失败');
+  }
+}
+
+const handleWhiteBgExportClick = () => launchExternalTool('/copy_800_tool', '白底图批量导出工具已启动')
+const handleProductAlbumClick = () => launchExternalTool('/product_list', '产品图册生成工具已启动')
+
 
 
 import { useCheckInfo } from '@/composables/userCheck';
@@ -1737,6 +1779,12 @@ const selectFolder = () => {
       font-weight: 500;
     }
 
+    &.external-tool {
+      cursor: pointer;
+      opacity: 1;
+      background: #fff;
+    }
+
     .tab-edit-btn {
       position: absolute;
       right: 10px;