init
This commit is contained in:
66
electron/config/config.default.ts
Normal file
66
electron/config/config.default.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import path from 'path';
|
||||
import {getBaseDir} from 'ee-core/ps';
|
||||
import {type AppConfig} from 'ee-core/config';
|
||||
|
||||
const config: () => AppConfig = () => {
|
||||
return {
|
||||
openDevTools: false,
|
||||
singleLock: true,
|
||||
windowsOption: {
|
||||
title: 'PQS9100工具箱', // 软件标题
|
||||
width: 980, // 软件窗口宽度
|
||||
height: 650, // 软件窗口高度
|
||||
minWidth: 800, // 软件窗口最小宽度
|
||||
minHeight: 650, // 软件窗口最小高度
|
||||
autoHideMenuBar: true, // 默认不显示菜单栏,
|
||||
webPreferences: {
|
||||
webSecurity: true,
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
},
|
||||
frame: true,
|
||||
show: false,
|
||||
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
|
||||
},
|
||||
logger: {
|
||||
level: 'INFO',
|
||||
outputJSON: false,
|
||||
appLogName: 'pqs-9100_tool.log',
|
||||
coreLogName: 'pqs-9100_tool-core.log',
|
||||
errorLogName: 'pqs-9100_tool-error.log',
|
||||
},
|
||||
remote: {
|
||||
enable: false,
|
||||
url: '',
|
||||
},
|
||||
socketServer: {
|
||||
enable: true,
|
||||
port: 7070,
|
||||
path: "/socket.io/",
|
||||
connectTimeout: 45000,
|
||||
pingTimeout: 30000,
|
||||
pingInterval: 25000,
|
||||
maxHttpBufferSize: 1e8,
|
||||
transports: ["polling", "websocket"],
|
||||
cors: {
|
||||
origin: true,
|
||||
},
|
||||
channel: 'socket-channel',
|
||||
},
|
||||
httpServer: {
|
||||
enable: true,
|
||||
https: {
|
||||
enable: false,
|
||||
key: '/public/ssl/localhost+1.key',
|
||||
cert: '/public/ssl/localhost+1.pem',
|
||||
},
|
||||
host: '127.0.0.1',
|
||||
port: 7071,
|
||||
},
|
||||
mainServer: {
|
||||
indexPath: '/public/dist/index.html',
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default config;
|
||||
14
electron/config/config.local.ts
Normal file
14
electron/config/config.local.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { type AppConfig } from 'ee-core/config';
|
||||
|
||||
const config: () => AppConfig = () => {
|
||||
return {
|
||||
openDevTools: {
|
||||
mode: 'bottom'
|
||||
},
|
||||
jobs: {
|
||||
messageLog: false
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default config;
|
||||
9
electron/config/config.prod.ts
Normal file
9
electron/config/config.prod.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { type AppConfig } from 'ee-core/config';
|
||||
|
||||
const config: () => AppConfig = () => {
|
||||
return {
|
||||
openDevTools: false,
|
||||
};
|
||||
};
|
||||
|
||||
export default config;
|
||||
63
electron/controller/cross.ts
Normal file
63
electron/controller/cross.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { crossService } from '../service/cross';
|
||||
|
||||
/**
|
||||
* Cross
|
||||
* @class
|
||||
*/
|
||||
class CrossController {
|
||||
|
||||
/**
|
||||
* View process service information
|
||||
*/
|
||||
info() {
|
||||
crossService.info();
|
||||
return 'hello electron-egg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service url
|
||||
*/
|
||||
async getUrl(args: { name: string }): Promise<string> {
|
||||
const { name } = args;
|
||||
const serverUrl = crossService.getUrl(name);
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* kill service
|
||||
* By default (modifiable), killing the process will exit the electron application.
|
||||
*/
|
||||
async killServer(args: { type: string; name: string }): Promise<void> {
|
||||
const { type, name } = args;
|
||||
crossService.killServer(type, name);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* create service
|
||||
*/
|
||||
async createServer(args: { program: string }): Promise<void> {
|
||||
const { program } = args;
|
||||
if (program == 'go') {
|
||||
crossService.createGoServer();
|
||||
} else if (program == 'java') {
|
||||
crossService.createJavaServer();
|
||||
} else if (program == 'python') {
|
||||
crossService.createPythonServer();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the api for the cross service
|
||||
*/
|
||||
async requestApi(args: { name: string; urlPath: string; params: any }): Promise<any> {
|
||||
const { name, urlPath, params} = args;
|
||||
const data = await crossService.requestApi(name, urlPath, params);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
CrossController.toString = () => '[class CrossController]';
|
||||
|
||||
export default CrossController;
|
||||
63
electron/controller/effect.ts
Normal file
63
electron/controller/effect.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { dialog } from 'electron';
|
||||
import { getMainWindow } from 'ee-core/electron';
|
||||
|
||||
/**
|
||||
* effect - demo
|
||||
* @class
|
||||
*/
|
||||
class EffectController {
|
||||
|
||||
/**
|
||||
* select file
|
||||
*/
|
||||
selectFile(): string | null {
|
||||
const filePaths = dialog.showOpenDialogSync({
|
||||
properties: ['openFile']
|
||||
});
|
||||
|
||||
if (!filePaths) {
|
||||
return null
|
||||
}
|
||||
|
||||
return filePaths[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* login window
|
||||
*/
|
||||
loginWindow(args: { width?: number; height?: number }): void {
|
||||
const { width, height } = args;
|
||||
const win = getMainWindow();
|
||||
|
||||
const size = {
|
||||
width: width || 400,
|
||||
height: height || 300
|
||||
}
|
||||
win.setSize(size.width, size.height);
|
||||
win.setResizable(true);
|
||||
win.center();
|
||||
win.show();
|
||||
win.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* restore window
|
||||
*/
|
||||
restoreWindow(args: { width?: number; height?: number }): void {
|
||||
const { width, height } = args;
|
||||
const win = getMainWindow();
|
||||
|
||||
const size = {
|
||||
width: width || 980,
|
||||
height: height || 650
|
||||
}
|
||||
win.setSize(size.width, size.height);
|
||||
win.setResizable(true);
|
||||
win.center();
|
||||
win.show();
|
||||
win.focus();
|
||||
}
|
||||
}
|
||||
EffectController.toString = () => '[class EffectController]';
|
||||
|
||||
export default EffectController;
|
||||
16
electron/controller/example.ts
Normal file
16
electron/controller/example.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* example
|
||||
* @class
|
||||
*/
|
||||
class ExampleController {
|
||||
|
||||
/**
|
||||
* test
|
||||
*/
|
||||
async test(): Promise<string> {
|
||||
return 'hello electron-egg';
|
||||
}
|
||||
}
|
||||
ExampleController.toString = () => '[class ExampleController]';
|
||||
|
||||
export default ExampleController;
|
||||
167
electron/controller/os.ts
Normal file
167
electron/controller/os.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { app as electronApp, dialog, shell } from 'electron';
|
||||
import { windowService } from '../service/os/window';
|
||||
|
||||
/**
|
||||
* example
|
||||
* @class
|
||||
*/
|
||||
class OsController {
|
||||
|
||||
/**
|
||||
* All methods receive two parameters
|
||||
* @param args Parameters transmitted by the frontend
|
||||
* @param event - Event are only available during IPC communication. For details, please refer to the controller documentation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Message prompt dialog box
|
||||
*/
|
||||
messageShow(): string {
|
||||
dialog.showMessageBoxSync({
|
||||
type: 'info', // "none", "info", "error", "question" 或者 "warning"
|
||||
title: 'Custom Title',
|
||||
message: 'Customize message content',
|
||||
detail: 'Other additional information'
|
||||
})
|
||||
|
||||
return 'Opened the message box';
|
||||
}
|
||||
|
||||
/**
|
||||
* Message prompt and confirmation dialog box
|
||||
*/
|
||||
messageShowConfirm(): string {
|
||||
const res = dialog.showMessageBoxSync({
|
||||
type: 'info',
|
||||
title: 'Custom Title',
|
||||
message: 'Customize message content',
|
||||
detail: 'Other additional information',
|
||||
cancelId: 1, // Index of buttons used to cancel dialog boxes
|
||||
defaultId: 0, // Set default selected button
|
||||
buttons: ['confirm', 'cancel'],
|
||||
})
|
||||
let data = (res === 0) ? 'click the confirm button' : 'click the cancel button';
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select Directory
|
||||
*/
|
||||
selectFolder() {
|
||||
const filePaths = dialog.showOpenDialogSync({
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
});
|
||||
|
||||
if (!filePaths) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return filePaths[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* open directory
|
||||
*/
|
||||
openDirectory(args: { id: any }): boolean {
|
||||
const { id } = args;
|
||||
if (!id) {
|
||||
return false;
|
||||
}
|
||||
let dir = '';
|
||||
if (path.isAbsolute(id)) {
|
||||
dir = id;
|
||||
} else {
|
||||
dir = electronApp.getPath(id);
|
||||
}
|
||||
|
||||
shell.openPath(dir);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select Picture
|
||||
*/
|
||||
selectPic(): string | null {
|
||||
const filePaths = dialog.showOpenDialogSync({
|
||||
title: 'select pic',
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
|
||||
]
|
||||
});
|
||||
if (!filePaths) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const data = fs.readFileSync(filePaths[0]);
|
||||
const pic = 'data:image/jpeg;base64,' + data.toString('base64');
|
||||
return pic;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new window
|
||||
*/
|
||||
createWindow(args: any): any {
|
||||
const wcid = windowService.createWindow(args);
|
||||
return wcid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Window contents id
|
||||
*/
|
||||
getWCid(args: any): any {
|
||||
const wcid = windowService.getWCid(args);
|
||||
return wcid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realize communication between two windows through the transfer of the main process
|
||||
*/
|
||||
window1ToWindow2(args: any): void {
|
||||
windowService.communicate(args);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realize communication between two windows through the transfer of the main process
|
||||
*/
|
||||
window2ToWindow1(args: any): void {
|
||||
windowService.communicate(args);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create system notifications
|
||||
*/
|
||||
sendNotification(args: { title?: string; subtitle?: string; body?: string; silent?: boolean }, event: any): boolean {
|
||||
const { title, subtitle, body, silent} = args;
|
||||
|
||||
const options: any = {};
|
||||
if (title) {
|
||||
options.title = title;
|
||||
}
|
||||
if (subtitle) {
|
||||
options.subtitle = subtitle;
|
||||
}
|
||||
if (body) {
|
||||
options.body = body;
|
||||
}
|
||||
if (silent !== undefined) {
|
||||
options.silent = silent;
|
||||
}
|
||||
windowService.createNotification(options, event);
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
OsController.toString = () => '[class OsController]';
|
||||
|
||||
export default OsController;
|
||||
10
electron/jobs/example/hello.ts
Normal file
10
electron/jobs/example/hello.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
|
||||
/**
|
||||
* Welcome function
|
||||
*/
|
||||
function welcome(): void {
|
||||
logger.info('[child-process] [jobs/example/hello] welcome !');
|
||||
}
|
||||
|
||||
export { welcome };
|
||||
98
electron/jobs/example/timer.ts
Normal file
98
electron/jobs/example/timer.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
import { isChildJob, exit } from 'ee-core/ps';
|
||||
import { childMessage } from 'ee-core/message';
|
||||
import { welcome } from './hello';
|
||||
import { UserService } from '../../service/job/user';
|
||||
import { sqlitedbService } from '../../service/database/sqlitedb';
|
||||
|
||||
/**
|
||||
* example - TimerJob
|
||||
* @class
|
||||
*/
|
||||
class TimerJob {
|
||||
timer: NodeJS.Timeout | undefined;
|
||||
timeoutTimer: NodeJS.Timeout | undefined;
|
||||
number: number;
|
||||
countdown: number;
|
||||
|
||||
constructor() {
|
||||
this.timer = undefined;
|
||||
this.timeoutTimer = undefined;
|
||||
this.number = 0;
|
||||
this.countdown = 10; // 倒计时
|
||||
sqlitedbService.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* handle() method is necessary and will be automatically called
|
||||
* params transferred parameters
|
||||
*/
|
||||
async handle(params: any): Promise<void> {
|
||||
logger.info("[child-process] TimerJob params: ", params);
|
||||
const { jobId } = params;
|
||||
|
||||
// Use service in child process
|
||||
// 1. Ensure that the service does not have Electron's API or dependencies, as Electron does not support them
|
||||
const userService = new UserService();
|
||||
userService.hello('job');
|
||||
|
||||
// Execute the task
|
||||
this.number = 0;
|
||||
this.countdown = 10;
|
||||
this.doTimer(jobId);
|
||||
|
||||
// sqlite
|
||||
const userList = await sqlitedbService.getAllTestDataSqlite();
|
||||
logger.info('[child-process] Sqlite userList:', userList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the job
|
||||
*/
|
||||
async pause(jobId: string): Promise<void> {
|
||||
logger.info("[child-process] Pause timerJob, jobId: ", jobId);
|
||||
clearInterval(this.timer);
|
||||
clearInterval(this.timeoutTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the job
|
||||
*/
|
||||
async resume(jobId: string, pid: number): Promise<void> {
|
||||
logger.info("[child-process] Resume timerJob, jobId: ", jobId, ", pid: ", pid);
|
||||
this.doTimer(jobId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task
|
||||
*/
|
||||
async doTimer(jobId) {
|
||||
// Timer to simulate the task
|
||||
const eventName = 'job-timer-progress-' + jobId;
|
||||
this.timer = setInterval(() => {
|
||||
welcome();
|
||||
|
||||
childMessage.send(eventName, {jobId, number: this.number, end: false});
|
||||
this.number++;
|
||||
this.countdown--;
|
||||
}, 1000);
|
||||
|
||||
// Use setTimeout to simulate the task duration
|
||||
this.timeoutTimer = setTimeout(() => {
|
||||
// Stop the timer to simulate the task
|
||||
clearInterval(this.timer);
|
||||
|
||||
// Task completed, reset the front-end display
|
||||
childMessage.send(eventName, {jobId, number:0, pid:0, end: true});
|
||||
|
||||
// If it is a childJob task, call exit() to exit the process, otherwise it will stay in memory
|
||||
// If it is a childPoolJob task, stay in memory and wait for the next business
|
||||
if (isChildJob()) {
|
||||
exit();
|
||||
}
|
||||
}, this.countdown * 1000)
|
||||
}
|
||||
}
|
||||
TimerJob.toString = () => '[class TimerJob]';
|
||||
|
||||
export default TimerJob;
|
||||
19
electron/main.ts
Normal file
19
electron/main.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { ElectronEgg } from 'ee-core';
|
||||
import { Lifecycle } from './preload/lifecycle';
|
||||
import { preload } from './preload';
|
||||
|
||||
// New app
|
||||
const app = new ElectronEgg();
|
||||
|
||||
// Register lifecycle
|
||||
const life = new Lifecycle();
|
||||
app.register("ready", life.ready);
|
||||
app.register("electron-app-ready", life.electronAppReady);
|
||||
app.register("window-ready", life.windowReady);
|
||||
app.register("before-close", life.beforeClose);
|
||||
|
||||
// Register preload
|
||||
app.register("preload", preload);
|
||||
|
||||
// Run
|
||||
app.run();
|
||||
16
electron/preload/bridge.ts
Normal file
16
electron/preload/bridge.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 如果启用了上下文隔离,渲染进程无法使用electron的api,
|
||||
* 可通过contextBridge 导出api给渲染进程使用
|
||||
*/
|
||||
|
||||
import { type IpcRenderer, contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
// 确保contextBridge.exposeInMainWorld的参数类型正确,这里进行简单的类型定义示例
|
||||
type ElectronApi = {
|
||||
ipcRenderer: IpcRenderer;
|
||||
};
|
||||
|
||||
const ele: ElectronApi = {
|
||||
ipcRenderer,
|
||||
};
|
||||
contextBridge.exposeInMainWorld('electron', ele);
|
||||
21
electron/preload/index.ts
Normal file
21
electron/preload/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Preload module, this file will be loaded when the program starts.
|
||||
*/
|
||||
|
||||
import {logger} from 'ee-core/log';
|
||||
import {trayService} from '../service/os/tray';
|
||||
import {securityService} from '../service/os/security';
|
||||
import {autoUpdaterService} from '../service/os/auto_updater';
|
||||
|
||||
function preload(): void {
|
||||
// Example feature module, optional to use and modify
|
||||
logger.info('[preload] load 5');
|
||||
trayService.create();
|
||||
securityService.create();
|
||||
autoUpdaterService.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point of the preload module
|
||||
*/
|
||||
export { preload };
|
||||
70
electron/preload/lifecycle.ts
Normal file
70
electron/preload/lifecycle.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { app as electronApp, screen } from 'electron';
|
||||
import { logger } from 'ee-core/log';
|
||||
import { getConfig } from 'ee-core/config';
|
||||
import { getMainWindow } from 'ee-core/electron';
|
||||
|
||||
class Lifecycle {
|
||||
/**
|
||||
* Core app has been loaded
|
||||
*/
|
||||
async ready(): Promise<void> {
|
||||
logger.info('[lifecycle] ready');
|
||||
}
|
||||
|
||||
/**
|
||||
* Electron app is ready
|
||||
*/
|
||||
async electronAppReady(): Promise<void> {
|
||||
logger.info('[lifecycle] electron-app-ready');
|
||||
|
||||
// When double clicking the icon, display the opened window
|
||||
electronApp.on('second-instance', () => {
|
||||
const win = getMainWindow();
|
||||
if (win.isMinimized()) {
|
||||
win.restore();
|
||||
}
|
||||
win.show();
|
||||
win.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main window has been loaded
|
||||
*/
|
||||
async windowReady(): Promise<void> {
|
||||
logger.info('[lifecycle] window-ready');
|
||||
|
||||
const win = getMainWindow();
|
||||
|
||||
// The window is centered and scaled proportionally
|
||||
// Obtain the size information of the main screen, calculate the width and height of the window as a percentage of the screen,
|
||||
// and calculate the coordinates of the upper left corner when the window is centered
|
||||
const mainScreen = screen.getPrimaryDisplay();
|
||||
const { width, height } = mainScreen.workAreaSize;
|
||||
const windowWidth = Math.floor(width * 0.6);
|
||||
const windowHeight = Math.floor(height * 0.8);
|
||||
const x = Math.floor((width - windowWidth) / 2);
|
||||
const y = Math.floor((height - windowHeight) / 2);
|
||||
win.setBounds({ x, y, width: windowWidth, height: windowHeight });
|
||||
|
||||
// Delay loading, no white screen
|
||||
const config = getConfig();
|
||||
const { windowsOption } = config;
|
||||
if (windowsOption?.show == false) {
|
||||
win.once('ready-to-show', () => {
|
||||
win.show();
|
||||
win.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Before app close
|
||||
*/
|
||||
async beforeClose(): Promise<void> {
|
||||
logger.info('[lifecycle] before-close');
|
||||
}
|
||||
}
|
||||
Lifecycle.toString = () => '[class Lifecycle]';
|
||||
|
||||
export { Lifecycle };
|
||||
144
electron/service/cross.ts
Normal file
144
electron/service/cross.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
import { getExtraResourcesDir, getLogDir } from 'ee-core/ps';
|
||||
import path from 'path';
|
||||
import axios from 'axios';
|
||||
import { is } from 'ee-core/utils';
|
||||
import { cross } from 'ee-core/cross';
|
||||
|
||||
/**
|
||||
* cross
|
||||
* @class
|
||||
*/
|
||||
class CrossService {
|
||||
|
||||
info(): string {
|
||||
const pids = cross.getPids();
|
||||
logger.info('cross pids:', pids);
|
||||
|
||||
let num = 1;
|
||||
pids.forEach(pid => {
|
||||
let entity = cross.getProc(pid);
|
||||
logger.info(`server-${num} name:${entity.name}`);
|
||||
logger.info(`server-${num} config:`, entity.config);
|
||||
num++;
|
||||
})
|
||||
|
||||
return 'hello electron-egg';
|
||||
}
|
||||
|
||||
getUrl(name: string): string {
|
||||
const serverUrl = cross.getUrl(name);
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
killServer(type: string, name: string): void {
|
||||
if (type == 'all') {
|
||||
cross.killAll();
|
||||
} else {
|
||||
cross.killByName(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create go service
|
||||
* In the default configuration, services can be started with applications.
|
||||
* Developers can turn off the configuration and create it manually.
|
||||
*/
|
||||
async createGoServer(): Promise<void> {
|
||||
// method 1: Use the default Settings
|
||||
//const entity = await cross.run(serviceName);
|
||||
|
||||
// method 2: Use custom configuration
|
||||
const serviceName = "go";
|
||||
const opt = {
|
||||
name: 'goapp',
|
||||
cmd: path.join(getExtraResourcesDir(), 'goapp'),
|
||||
directory: getExtraResourcesDir(),
|
||||
args: ['--port=7073'],
|
||||
appExit: true,
|
||||
}
|
||||
const entity = await cross.run(serviceName, opt);
|
||||
logger.info('server name:', entity.name);
|
||||
logger.info('server config:', entity.config);
|
||||
logger.info('server url:', entity.getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* create java server
|
||||
*/
|
||||
async createJavaServer(): Promise<void> {
|
||||
const serviceName = "java";
|
||||
const jarPath = path.join(getExtraResourcesDir(), 'java-app.jar');
|
||||
const opt = {
|
||||
name: 'javaapp',
|
||||
cmd: path.join(getExtraResourcesDir(), 'jre1.8.0_201/bin/javaw.exe'),
|
||||
directory: getExtraResourcesDir(),
|
||||
args: ['-jar', '-server', '-Xms512M', '-Xmx512M', '-Xss512k', '-Dspring.profiles.active=prod', `-Dserver.port=18080`, `-Dlogging.file.path=${getLogDir()}`, `${jarPath}`],
|
||||
appExit: false,
|
||||
}
|
||||
if (is.macOS()) {
|
||||
// Setup Java program
|
||||
opt.cmd = path.join(getExtraResourcesDir(), 'jre1.8.0_201.jre/Contents/Home/bin/java');
|
||||
}
|
||||
if (is.linux()) {
|
||||
// Setup Java program
|
||||
}
|
||||
|
||||
const entity = await cross.run(serviceName, opt);
|
||||
logger.info('server name:', entity.name);
|
||||
logger.info('server config:', entity.config);
|
||||
logger.info('server url:', cross.getUrl(entity.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* create python service
|
||||
* In the default configuration, services can be started with applications.
|
||||
* Developers can turn off the configuration and create it manually.
|
||||
*/
|
||||
async createPythonServer(): Promise<void> {
|
||||
// method 1: Use the default Settings
|
||||
//const entity = await cross.run(serviceName);
|
||||
|
||||
// method 2: Use custom configuration
|
||||
const serviceName = "python";
|
||||
const opt = {
|
||||
name: 'pyapp',
|
||||
cmd: path.join(getExtraResourcesDir(), 'py', 'pyapp'),
|
||||
directory: path.join(getExtraResourcesDir(), 'py'),
|
||||
args: ['--port=7074'],
|
||||
windowsExtname: true,
|
||||
appExit: true,
|
||||
}
|
||||
const entity = await cross.run(serviceName, opt);
|
||||
logger.info('server name:', entity.name);
|
||||
logger.info('server config:', entity.config);
|
||||
logger.info('server url:', entity.getUrl());
|
||||
}
|
||||
|
||||
async requestApi(name: string, urlPath: string, params: any): Promise<any> {
|
||||
const serverUrl = cross.getUrl(name);
|
||||
const apiHello = serverUrl + urlPath;
|
||||
console.log('Server Url:', serverUrl);
|
||||
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url: apiHello,
|
||||
timeout: 1000,
|
||||
params,
|
||||
proxy: false,
|
||||
});
|
||||
if (response.status == 200) {
|
||||
const { data } = response;
|
||||
return data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
CrossService.toString = () => '[class CrossService]';
|
||||
const crossService = new CrossService();
|
||||
|
||||
export {
|
||||
CrossService,
|
||||
crossService
|
||||
};
|
||||
23
electron/service/effect.ts
Normal file
23
electron/service/effect.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
|
||||
// effect service
|
||||
class EffectService {
|
||||
|
||||
// hello
|
||||
async hello(args: any): Promise<{ status: string; params: any }> {
|
||||
let obj = {
|
||||
status:'ok',
|
||||
params: args
|
||||
}
|
||||
logger.info('EffectService obj:', obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
EffectService.toString = () => '[class EffectService]';
|
||||
const effectService = new EffectService();
|
||||
|
||||
export {
|
||||
EffectService,
|
||||
effectService
|
||||
}
|
||||
21
electron/service/example.ts
Normal file
21
electron/service/example.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
|
||||
// example service
|
||||
class ExampleService {
|
||||
|
||||
async test(args: any): Promise<{ status: string; params: any }> {
|
||||
let obj = {
|
||||
status:'ok',
|
||||
params: args
|
||||
}
|
||||
logger.info('ExampleService obj:', obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
ExampleService.toString = () => '[class ExampleService]';
|
||||
const exampleService = new ExampleService();
|
||||
|
||||
export {
|
||||
ExampleService,
|
||||
exampleService
|
||||
};
|
||||
19
electron/service/job/user.ts
Normal file
19
electron/service/job/user.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
|
||||
/**
|
||||
* UserService class
|
||||
*/
|
||||
class UserService {
|
||||
|
||||
async hello(args: any): Promise<{ status: string; params: any }> {
|
||||
const obj = {
|
||||
status: 'ok',
|
||||
params: args,
|
||||
};
|
||||
logger.info('UserService obj:', obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
UserService.toString = () => '[class UserService]';
|
||||
|
||||
export { UserService };
|
||||
177
electron/service/os/auto_updater.ts
Normal file
177
electron/service/os/auto_updater.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { app as electronApp } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { is } from 'ee-core/utils';
|
||||
import { logger } from 'ee-core/log';
|
||||
import { getMainWindow, setCloseAndQuit } from 'ee-core/electron';
|
||||
|
||||
/**
|
||||
* AutoUpdaterService class for automatic updates
|
||||
*/
|
||||
class AutoUpdaterService {
|
||||
private config: {
|
||||
windows: boolean;
|
||||
macOS: boolean;
|
||||
linux: boolean;
|
||||
options: any;
|
||||
};
|
||||
constructor() {
|
||||
this.config = {
|
||||
windows: false,
|
||||
macOS: false,
|
||||
linux: false,
|
||||
options: {
|
||||
provider: 'generic',
|
||||
url: 'http://kodo.qiniu.com/'
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and configure the auto updater
|
||||
*/
|
||||
create(): void {
|
||||
logger.info('[autoUpdater] load');
|
||||
const cfg = this.config;
|
||||
if ((is.windows() && cfg.windows) ||
|
||||
(is.macOS() && cfg.macOS) ||
|
||||
(is.linux() && cfg.linux)) {
|
||||
// continue
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = {
|
||||
error: -1,
|
||||
available: 1,
|
||||
noAvailable: 2,
|
||||
downloading: 3,
|
||||
downloaded: 4,
|
||||
};
|
||||
|
||||
const version = electronApp.getVersion();
|
||||
logger.info('[autoUpdater] current version: ', version);
|
||||
|
||||
// Set the download server address
|
||||
let server = cfg.options.url;
|
||||
const lastChar = server.substring(server.length - 1);
|
||||
server = lastChar === '/' ? server : server + "/";
|
||||
cfg.options.url = server;
|
||||
|
||||
try {
|
||||
autoUpdater.setFeedURL(cfg.options);
|
||||
} catch (error) {
|
||||
logger.error('[autoUpdater] setFeedURL error : ', error);
|
||||
}
|
||||
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
// sendStatusToWindow('正在检查更新...');
|
||||
});
|
||||
autoUpdater.on('update-available', () => {
|
||||
const data = {
|
||||
status: status.available,
|
||||
desc: '有可用更新',
|
||||
};
|
||||
this.sendStatusToWindow(data);
|
||||
});
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
const data = {
|
||||
status: status.noAvailable,
|
||||
desc: '没有可用更新',
|
||||
};
|
||||
this.sendStatusToWindow(data);
|
||||
});
|
||||
autoUpdater.on('error', (err) => {
|
||||
const data = {
|
||||
status: status.error,
|
||||
desc: err,
|
||||
};
|
||||
this.sendStatusToWindow(data);
|
||||
});
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
const percentNumber = progressObj.percent;
|
||||
const totalSize = this.bytesChange(progressObj.total);
|
||||
const transferredSize = this.bytesChange(progressObj.transferred);
|
||||
let text = '已下载 ' + percentNumber + '%';
|
||||
text = text + ' (' + transferredSize + "/" + totalSize + ')';
|
||||
|
||||
const data = {
|
||||
status: status.downloading,
|
||||
desc: text,
|
||||
percentNumber,
|
||||
totalSize,
|
||||
transferredSize,
|
||||
};
|
||||
logger.info('[addon:autoUpdater] progress: ', text);
|
||||
this.sendStatusToWindow(data);
|
||||
});
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
const data = {
|
||||
status: status.downloaded,
|
||||
desc: '下载完成',
|
||||
};
|
||||
this.sendStatusToWindow(data);
|
||||
|
||||
// Allow the window to close
|
||||
setCloseAndQuit(true);
|
||||
|
||||
// Install updates and exit the application
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates
|
||||
*/
|
||||
checkUpdate(): void {
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Download updates
|
||||
*/
|
||||
download(): void {
|
||||
autoUpdater.downloadUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send status to the frontend
|
||||
*/
|
||||
sendStatusToWindow(content: any = {}): void {
|
||||
const textJson = JSON.stringify(content);
|
||||
const channel = 'custom/app/updater';
|
||||
const win = getMainWindow();
|
||||
win.webContents.send(channel, textJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bytes to a more readable format
|
||||
*/
|
||||
bytesChange(limit: number): string {
|
||||
let size = "";
|
||||
if (limit < 0.1 * 1024) {
|
||||
size = limit.toFixed(2) + "B";
|
||||
} else if (limit < 0.1 * 1024 * 1024) {
|
||||
size = (limit / 1024).toFixed(2) + "KB";
|
||||
} else if (limit < 0.1 * 1024 * 1024 * 1024) {
|
||||
size = (limit / (1024 * 1024)).toFixed(2) + "MB";
|
||||
} else {
|
||||
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB";
|
||||
}
|
||||
|
||||
let sizeStr = size + "";
|
||||
let index = sizeStr.indexOf(".");
|
||||
let dou = sizeStr.substring(index + 1, index + 3);
|
||||
if (dou === "00") {
|
||||
return sizeStr.substring(0, index) + sizeStr.substring(index + 3, index + 5);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
AutoUpdaterService.toString = () => '[class AutoUpdaterService]';
|
||||
const autoUpdaterService = new AutoUpdaterService();
|
||||
|
||||
export {
|
||||
AutoUpdaterService,
|
||||
autoUpdaterService
|
||||
};
|
||||
15
electron/service/os/icon.js
Normal file
15
electron/service/os/icon.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const {getMainWindow} = require("ee-core/electron");
|
||||
|
||||
|
||||
class IconService {
|
||||
|
||||
|
||||
|
||||
update(iconPath) {
|
||||
const win = getMainWindow();
|
||||
win.setIcon(iconPath);
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
iconService: new IconService()
|
||||
};
|
||||
31
electron/service/os/security.ts
Normal file
31
electron/service/os/security.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { logger } from 'ee-core/log';
|
||||
import { app as electronApp } from 'electron';
|
||||
|
||||
/**
|
||||
* SecurityService class for handling security-related operations
|
||||
*/
|
||||
class SecurityService {
|
||||
/**
|
||||
* Create and configure the security service
|
||||
*/
|
||||
create(): void {
|
||||
logger.info('[security] load');
|
||||
const runWithDebug = process.argv.find((e) => {
|
||||
const isHasDebug = e.includes('--inspect') || e.includes('--inspect-brk') || e.includes('--remote-debugging-port');
|
||||
return isHasDebug;
|
||||
});
|
||||
|
||||
// Do not allow remote debugging
|
||||
if (runWithDebug) {
|
||||
logger.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug);
|
||||
electronApp.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
SecurityService.toString = () => '[class SecurityService]';
|
||||
const securityService = new SecurityService();
|
||||
|
||||
export {
|
||||
SecurityService,
|
||||
securityService
|
||||
};
|
||||
81
electron/service/os/tray.ts
Normal file
81
electron/service/os/tray.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Tray, Menu } from 'electron';
|
||||
import path from 'path';
|
||||
import { isDev, getBaseDir } from 'ee-core/ps';
|
||||
import { logger } from 'ee-core/log';
|
||||
import { app as electronApp } from 'electron';
|
||||
import { getMainWindow, getCloseAndQuit, setCloseAndQuit } from 'ee-core/electron';
|
||||
|
||||
/**
|
||||
* 托盘
|
||||
* @class
|
||||
*/
|
||||
class TrayService {
|
||||
tray: Tray | null;
|
||||
config: {
|
||||
title: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.tray = null;
|
||||
this.config = {
|
||||
title: 'electron-egg',
|
||||
icon: '/public/images/tray.png',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the tray icon
|
||||
*/
|
||||
create () {
|
||||
logger.info('[tray] load');
|
||||
|
||||
const cfg = this.config;
|
||||
const mainWindow = getMainWindow();
|
||||
|
||||
// tray icon
|
||||
const iconPath = path.join(getBaseDir(), cfg.icon);
|
||||
|
||||
// Tray menu items
|
||||
const trayMenuTemplate = [
|
||||
{
|
||||
label: '显示',
|
||||
click: function () {
|
||||
mainWindow.show();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '退出',
|
||||
click: function () {
|
||||
electronApp.quit();
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// Set a flag to minimize to tray instead of closing
|
||||
setCloseAndQuit(false);
|
||||
mainWindow.on('close', (event: any) => {
|
||||
if (getCloseAndQuit()) {
|
||||
return;
|
||||
}
|
||||
mainWindow.hide();
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Initialize the tray
|
||||
this.tray = new Tray(iconPath);
|
||||
this.tray.setToolTip(cfg.title);
|
||||
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
|
||||
this.tray.setContextMenu(contextMenu);
|
||||
// Show the main window when the tray icon is clicked
|
||||
this.tray.on('click', () => {
|
||||
mainWindow.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
TrayService.toString = () => '[class TrayService]';
|
||||
const trayService = new TrayService();
|
||||
|
||||
export {
|
||||
trayService
|
||||
}
|
||||
131
electron/service/os/window.ts
Normal file
131
electron/service/os/window.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import path from 'path';
|
||||
import { BrowserWindow, Notification } from 'electron';
|
||||
import { getMainWindow } from 'ee-core/electron';
|
||||
import { isProd, getBaseDir } from 'ee-core/ps';
|
||||
import { getConfig } from 'ee-core/config';
|
||||
import { isFileProtocol } from 'ee-core/utils';
|
||||
import { logger } from 'ee-core/log';
|
||||
|
||||
/**
|
||||
* Window
|
||||
* @class
|
||||
*/
|
||||
class WindowService {
|
||||
myNotification: Notification | null;
|
||||
windows: { [key: string]: BrowserWindow };
|
||||
|
||||
constructor() {
|
||||
this.myNotification = null;
|
||||
this.windows = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new window
|
||||
*/
|
||||
createWindow(args: { type: string; content: string; windowName: string; windowTitle: string }): number {
|
||||
const { type, content, windowName, windowTitle } = args;
|
||||
let contentUrl: string = '';
|
||||
if (type == 'html') {
|
||||
contentUrl = path.join('file://', getBaseDir(), content)
|
||||
} else if (type == 'web') {
|
||||
contentUrl = content;
|
||||
} else if (type == 'vue') {
|
||||
let addr = 'http://localhost:8080'
|
||||
if (isProd()) {
|
||||
const { mainServer } = getConfig();
|
||||
if (mainServer && mainServer.protocol && isFileProtocol(mainServer.protocol)) {
|
||||
addr = mainServer.protocol + path.join(getBaseDir(), mainServer.indexPath);
|
||||
}
|
||||
}
|
||||
|
||||
contentUrl = addr + content;
|
||||
}
|
||||
|
||||
logger.info('[createWindow] url: ', contentUrl);
|
||||
const opt = {
|
||||
title: windowTitle,
|
||||
x: 10,
|
||||
y: 10,
|
||||
width: 980,
|
||||
height: 650,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
},
|
||||
}
|
||||
const win = new BrowserWindow(opt);
|
||||
const winContentsId = win.webContents.id;
|
||||
win.loadURL(contentUrl);
|
||||
win.webContents.openDevTools();
|
||||
this.windows[windowName] = win;
|
||||
|
||||
return winContentsId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get window contents id
|
||||
*/
|
||||
getWCid(args: { windowName: string }): number {
|
||||
const { windowName } = args;
|
||||
let win: BrowserWindow;
|
||||
if (windowName == 'main') {
|
||||
win = getMainWindow();
|
||||
} else {
|
||||
win = this.windows[windowName];
|
||||
}
|
||||
|
||||
return win.webContents.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realize communication between two windows through the transfer of the main process
|
||||
*/
|
||||
communicate(args: { receiver: string; content: any }): void {
|
||||
const { receiver, content } = args;
|
||||
if (receiver == 'main') {
|
||||
const win = getMainWindow();
|
||||
win.webContents.send('controller/os/window2ToWindow1', content);
|
||||
} else if (receiver == 'window2') {
|
||||
const win = this.windows[receiver];
|
||||
win.webContents.send('controller/os/window1ToWindow2', content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createNotification
|
||||
*/
|
||||
createNotification(options: any, event: any): void {
|
||||
const channel = 'controller/os/sendNotification';
|
||||
this.myNotification = new Notification(options);
|
||||
|
||||
if (options.clickEvent) {
|
||||
this.myNotification.on('click', () => {
|
||||
let data = {
|
||||
type: 'click',
|
||||
msg: '您点击了通知消息'
|
||||
}
|
||||
event.reply(`${channel}`, data)
|
||||
});
|
||||
}
|
||||
|
||||
if (options.closeEvent) {
|
||||
this.myNotification.on('close', () => {
|
||||
let data = {
|
||||
type: 'close',
|
||||
msg: '您关闭了通知消息'
|
||||
}
|
||||
event.reply(`${channel}`, data)
|
||||
});
|
||||
}
|
||||
|
||||
this.myNotification.show();
|
||||
}
|
||||
|
||||
}
|
||||
WindowService.toString = () => '[class WindowService]';
|
||||
const windowService = new WindowService();
|
||||
|
||||
export {
|
||||
WindowService,
|
||||
windowService
|
||||
}
|
||||
Reference in New Issue
Block a user