| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- 'use strict';
- const { Controller } = require('ee-core');
- const { shell } = require('electron');
- const Addon = require('ee-core/addon');
- const { dialog } = require('electron');
- 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();
- const errData = {
- msg :'请求失败,请联系管理员',
- code:999
- }
- const sharp = require('sharp'); // 确保安装:npm install sharp
- /**
- * example
- * @class
- */
- class UtilsController extends Controller {
- constructor(ctx) {
- 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 || '运行外部工具失败'
- };
- }
- }
- /**
- * 所有方法接收两个参数
- * @param args 前端传的参数
- * @param event - ipc通信时才有值。详情见:控制器文档
- */
- /**
- * upload
- */
- async shellFun (params) {
- // 如果是打开路径操作,确保路径存在
- if (params.action === 'openMkPath') {
- try {
- // 确保目录存在,如果不存在就创建
- if (!fs.existsSync(params.params)) {
- fs.mkdirSync(params.params, { recursive: true });
- }
- shell.openPath(params.params)
- return;
- } catch (err) {
- console.error('创建目录失败:', err);
- // 即使创建目录失败,也尝试打开路径(可能已经存在)
- }
- }
- shell[params.action](params.params)
- }
- async openMain (config) {
- const { id, url } = config;
- if (this.app.electron[id]) {
- const win = this.app.electron[id];
- // 切换到指定的 URL
- await win.loadURL(url);
- win.focus();
- win.show();
- return;
- }
- const win = new BrowserWindow({
- ...config,
- webPreferences: {
- webSecurity: false,
- contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge)
- nodeIntegration: true,
- // preload: path.join('../preload/preload.js','../preload/bridge.js'),
- },
- });
- await win.loadURL(config.url); // 设置窗口的 URL
- // 监听窗口关闭事件
- if(configDeault.debug) win.webContents.openDevTools()
- //
- win.on('close', () => {
- delete this.app.electron[config.id]; // 删除窗口引用
- });
- this.app.electron[config.id] = win ;
- }
- async openDirectory(optiops={
- title:"选择文件夹"
- }){
- const filePaths = dialog.showOpenDialogSync({
- title:optiops.title || '选择文件夹',
- properties: ['openDirectory']
- })
- if(filePaths[0]) return filePaths[0];
- return filePaths
- }
- /**
- * 关闭所有子窗口
- */
- closeAllWindows() {
- try {
- // 获取所有窗口
- const windows = this.app.electron;
- // 关闭除主窗口外的所有窗口
- for (const [id, window] of Object.entries(windows)) {
- if (!['mainWindow','extra'].includes(id)) { // 保留主窗口
- try {
- window.close();
- delete this.app.electron[id];
- } catch (error) {
- console.error(`关闭窗口 ${id} 失败:`, error);
- }
- }
- }
- console.log('所有子窗口已关闭');
- return { success: true, message: '所有子窗口已关闭' };
- } catch (error) {
- console.error('关闭所有窗口失败:', error);
- return { success: false, message: '关闭所有窗口失败: ' + error.message };
- }
- }
- async openImage(
- optiops= {
- title:"选择图片",
- filters:[
- { name: '支持JPG,png,gif', extensions: ['jpg','jpeg','png'] },
- ],
- }
- ){
- const filePaths = dialog.showOpenDialogSync({
- title:optiops.title || '选择图片',
- properties:['openFile'],
- filters:optiops.filters || [
- { name: '支持JPG,png,gif', extensions: ['jpg','jpeg','png'] },
- ]
- })
- const filePath = filePaths[0];
- const fileBuffer = fs.readFileSync(filePath);
- const base64Image = fileBuffer.toString('base64');
- // 获取文件扩展名
- const extension = path.extname(filePath).toLowerCase().replace('.', '');
- // 根据扩展名确定 MIME 类型
- let mimeType = '';
- switch (extension) {
- case 'jpg':
- case 'jpeg':
- mimeType = 'image/jpeg';
- break;
- case 'png':
- mimeType = 'image/png';
- break;
- case 'gif':
- mimeType = 'image/gif';
- break;
- default:
- mimeType = 'application/octet-stream'; // 默认 MIME 类型
- break;
- }
- // 构建 data URL
- const dataUrl = `data:${mimeType};base64,${base64Image}`;
- return {
- filePath:filePath,
- base64Image:dataUrl
- };
- }
- /**
- * 将前端生成的详情图数据写入 EXE 同级目录下的 output 文件夹。
- * @param {{ bundles: Array }} payload
- */
- async saveGeneratedImages (payload = {}) {
- try {
- const { app } = require('electron');
- const bundles = payload.bundles || [];
- if (!Array.isArray(bundles) || !bundles.length) {
- return { code: 1, msg: '无可保存的图片数据' };
- }
- // 运行目录:打包后为 EXE 所在目录,开发环境为项目根目录
- const isPackaged = app.isPackaged;
- const exeDir = isPackaged ? path.dirname(app.getPath('exe')) : process.cwd();
- const baseOutputDir = path.join(exeDir, 'output');
- if (!fs.existsSync(baseOutputDir)) {
- fs.mkdirSync(baseOutputDir, { recursive: true });
- }
- let fileCount = 0;
- const saveDataUrlToFile = (dataUrl, targetPath) => {
- if (!dataUrl || typeof dataUrl !== 'string') return;
- const match = dataUrl.match(/^data:image\/\w+;base64,(.+)$/);
- const base64Data = match ? match[1] : dataUrl;
- const buffer = Buffer.from(base64Data, 'base64');
- const dir = path.dirname(targetPath);
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir, { recursive: true });
- }
- fs.writeFileSync(targetPath, buffer);
- fileCount += 1;
- };
- bundles.forEach(bundle => {
- if (!bundle) return;
- const styleNo = (bundle.styleNo || bundle.styleKey || 'UNKNOWN').toString().replace(/[\\\/:*?"<>|]/g, '_');
- const styleDir = path.join(baseOutputDir, styleNo);
- if (!fs.existsSync(styleDir)) {
- fs.mkdirSync(styleDir, { recursive: true });
- }
- // 单画布图片
- (bundle.images || []).forEach((img, idx) => {
- if (!img || typeof img.dataUrl !== 'string') return;
- if (img.dataUrl === 'model' || img.dataUrl === 'scene') return;
- if (!img.dataUrl.startsWith('data:image')) return;
- const fileName = `canvas_${img.canvasIndex || 0}_img_${idx}.jpg`;
- const targetPath = path.join(styleDir, fileName);
- saveDataUrlToFile(img.dataUrl, targetPath);
- });
- // 合成长图
- if (bundle.combined && bundle.combined.dataUrl) {
- const combinedPath = path.join(styleDir, 'all_canvases.jpg');
- saveDataUrlToFile(bundle.combined.dataUrl, combinedPath);
- }
- });
- return {
- code: 0,
- data: {
- outputDir: baseOutputDir,
- fileCount,
- },
- };
- } catch (error) {
- console.error('saveGeneratedImages error:', error);
- return {
- code: 1,
- msg: error.message || '保存生成图片失败',
- };
- }
- }
- async openFile(optiops= {
- title:"选择文件",
- filters:[
- { name: '支持JPG', extensions: ['jpg','jpeg'] },
- ],
- }){
- const filePaths = dialog.showOpenDialogSync({
- title:optiops.title || '选择文件',
- properties: ['openFile'],
- filters: optiops.filters || [
- { name: '选择文件' },
- ]
- })
- if(filePaths[0]) return filePaths[0];
- return filePaths
- }
- getAppConfig(){
- const config = readConfigFile()
- const appPath = path.join(app.getAppPath(), '../..');
- const pyPath = path.join(path.dirname(appPath), 'extraResources', 'py');
- return {
- ...config,
- userDataPath: app.getPath('userData'),
- appPath: appPath,
- pyPath:pyPath,
- }
- }
- async readFileImageForPath(filePath,maxWidth=1500){
- const getMimeType = (fileName)=>{
- const extension = path.extname(fileName).toLowerCase().replace('.', '');
- let mimeType = '';
- switch (extension) {
- case 'jpg':
- case 'jpeg':
- mimeType = 'image/jpeg';
- break;
- case 'png':
- mimeType = 'image/png';
- break;
- case 'gif':
- mimeType = 'image/gif';
- break;
- case 'webp':
- mimeType = 'image/webp';
- break;
- case 'avif':
- mimeType = 'image/avif';
- break;
- default:
- mimeType = 'application/octet-stream';
- break;
- }
- return mimeType;
- }
- try {
- const fileName = path.basename(filePath);
- const image = sharp(filePath);
- const metadata = await image.metadata();
- let mimeType = getMimeType(fileName); // 调用下面定义的私有方法获取 MIME 类型
- let fileBuffer;
- if (metadata.width > maxWidth) {
- // 如果宽度大于 1500px,压缩至 1500px 宽度,保持比例
- fileBuffer = await image.resize(maxWidth).toBuffer();
- } else {
- // 否则直接读取原图
- fileBuffer = fs.readFileSync(filePath);
- }
- return {
- fileBuffer,
- fileName,
- mimeType
- };
- } catch (error) {
- console.error('Error processing image:', error);
- throw error;
- }
- }
- }
- UtilsController.toString = () => '[class ExampleController]';
- module.exports = UtilsController;
|