diff --git a/build/extraResources/chromeExtension/read.txt b/build/extraResources/chromeExtension/read.txt
new file mode 100644
index 0000000..57b4b84
--- /dev/null
+++ b/build/extraResources/chromeExtension/read.txt
@@ -0,0 +1 @@
+chrome应用商店ctx文件,解压后,放置在此目录中,打包时会将资源加入安装包内。
\ No newline at end of file
diff --git a/build/extraResources/dll/myDllDemo.dll b/build/extraResources/dll/myDllDemo.dll
new file mode 100644
index 0000000..ffd5446
Binary files /dev/null and b/build/extraResources/dll/myDllDemo.dll differ
diff --git a/build/extraResources/goapp b/build/extraResources/goapp
new file mode 100755
index 0000000..fbb62d0
Binary files /dev/null and b/build/extraResources/goapp differ
diff --git a/build/extraResources/read.txt b/build/extraResources/read.txt
new file mode 100644
index 0000000..e20ade3
--- /dev/null
+++ b/build/extraResources/read.txt
@@ -0,0 +1 @@
+建议第三方软件放置在此目录中,打包时会将资源加入安装包内。
\ No newline at end of file
diff --git a/build/icons/128x128.png b/build/icons/128x128.png
new file mode 100644
index 0000000..56b37e1
Binary files /dev/null and b/build/icons/128x128.png differ
diff --git a/build/icons/16x16.png b/build/icons/16x16.png
new file mode 100644
index 0000000..fc40055
Binary files /dev/null and b/build/icons/16x16.png differ
diff --git a/build/icons/256x256.png b/build/icons/256x256.png
new file mode 100644
index 0000000..95dc60b
Binary files /dev/null and b/build/icons/256x256.png differ
diff --git a/build/icons/32x32.png b/build/icons/32x32.png
new file mode 100644
index 0000000..ea7b2df
Binary files /dev/null and b/build/icons/32x32.png differ
diff --git a/build/icons/48x48.png b/build/icons/48x48.png
new file mode 100644
index 0000000..a85fc93
Binary files /dev/null and b/build/icons/48x48.png differ
diff --git a/build/icons/512x512.png b/build/icons/512x512.png
new file mode 100644
index 0000000..df48e85
Binary files /dev/null and b/build/icons/512x512.png differ
diff --git a/build/icons/64x64.png b/build/icons/64x64.png
new file mode 100644
index 0000000..87b8273
Binary files /dev/null and b/build/icons/64x64.png differ
diff --git a/build/icons/icon.icns b/build/icons/icon.icns
new file mode 100644
index 0000000..aa79460
Binary files /dev/null and b/build/icons/icon.icns differ
diff --git a/build/icons/icon.ico b/build/icons/icon.ico
new file mode 100644
index 0000000..aefab6a
Binary files /dev/null and b/build/icons/icon.ico differ
diff --git a/build/icons/icon.png b/build/icons/icon.png
new file mode 100644
index 0000000..df48e85
Binary files /dev/null and b/build/icons/icon.png differ
diff --git a/build/script/installer.nsh b/build/script/installer.nsh
new file mode 100644
index 0000000..e69de29
diff --git a/cmd/bin.js b/cmd/bin.js
new file mode 100644
index 0000000..c00e5a3
--- /dev/null
+++ b/cmd/bin.js
@@ -0,0 +1,195 @@
+/**
+ * ee-bin 配置
+ * 仅适用于开发环境
+ */
+module.exports = {
+ /**
+ * development serve ("frontend" "electron" )
+ * ee-bin dev
+ */
+ dev: {
+ frontend: {
+ directory: './frontend',
+ cmd: 'npm',
+ args: ['run', 'dev'],
+ port: 8080,
+ },
+ electron: {
+ directory: './',
+ cmd: 'electron',
+ args: ['.', '--env=local'],
+ watch: true,
+ }
+ },
+
+ /**
+ * 构建
+ * ee-bin build
+ */
+ build: {
+ frontend: {
+ directory: './frontend',
+ cmd: 'npm',
+ args: ['run', 'build'],
+ },
+ electron: {
+ type: 'javascript',
+ bundleType: 'copy'
+ },
+ win64: {
+ cmd: 'electron-builder',
+ directory: './',
+ args: ['--config=./cmd/builder.json', '-w=nsis', '--x64'],
+ },
+ win32: {
+ args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'],
+ },
+ win_e: {
+ args: ['--config=./cmd/builder.json', '-w=portable', '--x64'],
+ },
+ win_7z: {
+ args: ['--config=./cmd/builder.json', '-w=7z', '--x64'],
+ },
+ mac: {
+ args: ['--config=./cmd/builder-mac.json', '-m'],
+ },
+ mac_arm64: {
+ args: ['--config=./cmd/builder-mac-arm64.json', '-m', '--arm64'],
+ },
+ linux: {
+ args: ['--config=./cmd/builder-linux.json', '-l=deb', '--x64'],
+ },
+ linux_arm64: {
+ args: ['--config=./cmd/builder-linux.json', '-l=deb', '--arm64'],
+ },
+ go_w: {
+ directory: './go',
+ cmd: 'go',
+ args: ['build', '-o=../build/extraResources/goapp.exe'],
+ },
+ go_m: {
+ directory: './go',
+ cmd: 'go',
+ args: ['build', '-o=../build/extraResources/goapp'],
+ },
+ go_l: {
+ directory: './go',
+ cmd: 'go',
+ args: ['build', '-o=../build/extraResources/goapp'],
+ },
+ python: {
+ directory: './python',
+ cmd: 'python',
+ args: ['./setup.py', 'build'],
+ },
+ },
+
+ /**
+ * 移动资源
+ * ee-bin move
+ */
+ move: {
+ frontend_dist: {
+ src: './frontend/dist',
+ dest: './public/dist'
+ },
+ go_static: {
+ src: './frontend/dist',
+ dest: './go/public/dist'
+ },
+ go_config: {
+ src: './go/config',
+ dest: './go/public/config'
+ },
+ go_package: {
+ src: './package.json',
+ dest: './go/public/package.json'
+ },
+ go_images: {
+ src: './public/images',
+ dest: './go/public/images'
+ },
+ python_dist: {
+ src: './python/dist',
+ dest: './build/extraResources/py'
+ },
+ },
+
+ /**
+ * 预发布模式(prod)
+ * ee-bin start
+ */
+ start: {
+ directory: './',
+ cmd: 'electron',
+ args: ['.', '--env=prod']
+ },
+
+ /**
+ * 加密
+ */
+ encrypt: {
+ frontend: {
+ type: 'none',
+ files: [
+ './public/dist/**/*.(js|json)',
+ ],
+ cleanFiles: ['./public/dist'],
+ confusionOptions: {
+ compact: true,
+ stringArray: true,
+ stringArrayEncoding: ['none'],
+ stringArrayCallsTransform: true,
+ numbersToExpressions: true,
+ target: 'browser',
+ }
+ },
+ electron: {
+ type: 'confusion',
+ files: [
+ './public/electron/**/*.(js|json)',
+ ],
+ cleanFiles: ['./public/electron'],
+ confusionOptions: {
+ compact: true,
+ stringArray: true,
+ stringArrayEncoding: ['rc4'],
+ deadCodeInjection: false,
+ stringArrayCallsTransform: true,
+ numbersToExpressions: true,
+ target: 'node',
+ }
+ }
+ },
+
+ /**
+ * 执行自定义命令
+ * ee-bin exec
+ */
+ exec: {
+ // 单独调试,air 实现 go 热重载
+ go: {
+ directory: './go',
+ cmd: 'air',
+ args: ['-c=config/.air.toml' ],
+ },
+ // windows 单独调试,air 实现 go 热重载
+ go_w: {
+ directory: './go',
+ cmd: 'air',
+ args: ['-c=config/.air.windows.toml' ],
+ },
+ // 单独调试,以基础方式启动 go
+ go2: {
+ directory: './go',
+ cmd: 'go',
+ args: ['run', './main.go', '--env=dev','--basedir=../', '--port=7073'],
+ },
+ python: {
+ directory: './python',
+ cmd: 'python',
+ args: ['./main.py', '--port=7074'],
+ stdio: "inherit", // ignore
+ },
+ },
+};
\ No newline at end of file
diff --git a/cmd/builder-linux.json b/cmd/builder-linux.json
new file mode 100644
index 0000000..eb4dfb2
--- /dev/null
+++ b/cmd/builder-linux.json
@@ -0,0 +1,40 @@
+{
+ "productName": "ee",
+ "appId": "com.bilibili.ee",
+ "copyright": "© 2025 duola Technology Co., Ltd.",
+ "directories": {
+ "output": "out"
+ },
+ "asar": true,
+ "files": [
+ "**/*",
+ "!cmd/",
+ "!data/",
+ "!electron/",
+ "!frontend/",
+ "!logs/",
+ "!out/",
+ "!go/",
+ "!python/"
+ ],
+ "extraResources": [
+ {
+ "from": "build/extraResources",
+ "to": "extraResources"
+ }
+ ],
+ "publish": [
+ {
+ "provider": "generic",
+ "url": ""
+ }
+ ],
+ "linux": {
+ "icon": "build/icons/icon.icns",
+ "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
+ "target": [
+ "deb"
+ ],
+ "category": "Utility"
+ }
+}
\ No newline at end of file
diff --git a/cmd/builder-mac-arm64.json b/cmd/builder-mac-arm64.json
new file mode 100644
index 0000000..b110fc6
--- /dev/null
+++ b/cmd/builder-mac-arm64.json
@@ -0,0 +1,38 @@
+{
+ "productName": "ee",
+ "appId": "com.bilibili.ee",
+ "copyright": "© 2025 duola Technology Co., Ltd.",
+ "directories": {
+ "output": "out"
+ },
+ "asar": true,
+ "files": [
+ "**/*",
+ "!cmd/",
+ "!data/",
+ "!electron/",
+ "!frontend/",
+ "!logs/",
+ "!out/",
+ "!go/",
+ "!python/"
+ ],
+ "extraResources": [
+ {
+ "from": "build/extraResources",
+ "to": "extraResources"
+ }
+ ],
+ "publish": [
+ {
+ "provider": "generic",
+ "url": ""
+ }
+ ],
+ "mac": {
+ "icon": "build/icons/icon.icns",
+ "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
+ "darkModeSupport": true,
+ "hardenedRuntime": false
+ }
+}
\ No newline at end of file
diff --git a/cmd/builder-mac.json b/cmd/builder-mac.json
new file mode 100644
index 0000000..b110fc6
--- /dev/null
+++ b/cmd/builder-mac.json
@@ -0,0 +1,38 @@
+{
+ "productName": "ee",
+ "appId": "com.bilibili.ee",
+ "copyright": "© 2025 duola Technology Co., Ltd.",
+ "directories": {
+ "output": "out"
+ },
+ "asar": true,
+ "files": [
+ "**/*",
+ "!cmd/",
+ "!data/",
+ "!electron/",
+ "!frontend/",
+ "!logs/",
+ "!out/",
+ "!go/",
+ "!python/"
+ ],
+ "extraResources": [
+ {
+ "from": "build/extraResources",
+ "to": "extraResources"
+ }
+ ],
+ "publish": [
+ {
+ "provider": "generic",
+ "url": ""
+ }
+ ],
+ "mac": {
+ "icon": "build/icons/icon.icns",
+ "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
+ "darkModeSupport": true,
+ "hardenedRuntime": false
+ }
+}
\ No newline at end of file
diff --git a/cmd/builder.json b/cmd/builder.json
new file mode 100644
index 0000000..c1c2a05
--- /dev/null
+++ b/cmd/builder.json
@@ -0,0 +1,50 @@
+{
+ "productName": "ee",
+ "appId": "com.electron.ee",
+ "copyright": "© 2025 duola Technology Co., Ltd.",
+ "directories": {
+ "output": "out"
+ },
+ "asar": true,
+ "files": [
+ "**/*",
+ "!cmd/",
+ "!data/",
+ "!electron/",
+ "!frontend/",
+ "!logs/",
+ "!out/",
+ "!go/",
+ "!python/"
+ ],
+ "extraResources": {
+ "from": "build/extraResources/",
+ "to": "extraResources"
+ },
+ "nsis": {
+ "oneClick": false,
+ "allowElevation": true,
+ "allowToChangeInstallationDirectory": true,
+ "installerIcon": "build/icons/icon.ico",
+ "uninstallerIcon": "build/icons/icon.ico",
+ "installerHeaderIcon": "build/icons/icon.ico",
+ "createDesktopShortcut": true,
+ "createStartMenuShortcut": true,
+ "shortcutName": "ee"
+ },
+ "publish": [
+ {
+ "provider": "generic",
+ "url": "https://github.com/wallace5303/electron-egg"
+ }
+ ],
+ "win": {
+ "icon": "build/icons/icon.ico",
+ "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
+ "target": [
+ {
+ "target": "nsis"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/electron/config/config.default.js b/electron/config/config.default.js
new file mode 100644
index 0000000..70b4719
--- /dev/null
+++ b/electron/config/config.default.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const path = require('path');
+const { getBaseDir } = require('ee-core/ps');
+
+/**
+ * 默认配置
+ */
+module.exports = () => {
+ return {
+ openDevTools: false,
+ singleLock: true,
+ windowsOption: {
+ title: 'electron-egg',
+ width: 980,
+ height: 650,
+ minWidth: 400,
+ minHeight: 300,
+ webPreferences: {
+ //webSecurity: false,
+ contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge)
+ nodeIntegration: true,
+ //preload: path.join(getElectronDir(), 'preload', 'bridge.js'),
+ },
+ frame: true,
+ show: true,
+ icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
+ },
+ logger: {
+ level: 'INFO',
+ outputJSON: false,
+ appLogName: 'ee.log',
+ coreLogName: 'ee-core.log',
+ errorLogName: 'ee-error.log'
+ },
+ remote: {
+ enable: false,
+ url: 'http://electron-egg.kaka996.com/'
+ },
+ socketServer: {
+ enable: false,
+ port: 7070,
+ path: "/socket.io/",
+ connectTimeout: 45000,
+ pingTimeout: 30000,
+ pingInterval: 25000,
+ maxHttpBufferSize: 1e8,
+ transports: ["polling", "websocket"],
+ cors: {
+ origin: true,
+ },
+ channel: 'socket-channel'
+ },
+ httpServer: {
+ enable: false,
+ 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',
+ channelSeparator: '/',
+ }
+ }
+}
diff --git a/electron/config/config.local.js b/electron/config/config.local.js
new file mode 100644
index 0000000..0822a50
--- /dev/null
+++ b/electron/config/config.local.js
@@ -0,0 +1,15 @@
+'use strict';
+
+/**
+ * Development environment configuration, coverage config.default.js
+ */
+module.exports = () => {
+ return {
+ openDevTools: {
+ mode: 'bottom'
+ },
+ jobs: {
+ messageLog: false
+ }
+ };
+};
diff --git a/electron/config/config.prod.js b/electron/config/config.prod.js
new file mode 100644
index 0000000..db1ce01
--- /dev/null
+++ b/electron/config/config.prod.js
@@ -0,0 +1,10 @@
+'use strict';
+
+/**
+ * coverage config.default.js
+ */
+module.exports = () => {
+ return {
+ openDevTools: false,
+ };
+};
diff --git a/electron/controller/cross.js b/electron/controller/cross.js
new file mode 100644
index 0000000..4bd7465
--- /dev/null
+++ b/electron/controller/cross.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const { crossService } = require('../service/cross');
+
+/**
+ * Cross
+ * @class
+ */
+class CrossController {
+
+ /**
+ * View process service information
+ */
+ info() {
+ crossService.info();
+ return 'hello electron-egg';
+ }
+
+ /**
+ * Get service url
+ */
+ async getUrl(args) {
+ 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) {
+ const { type, name } = args;
+ crossService.killServer(type, name);
+ return;
+ }
+
+ /**
+ * create service
+ */
+ async createServer(args) {
+ 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) {
+ const { name, urlPath, params} = args;
+ const data = await crossService.requestApi(name, urlPath, params);
+ return data;
+ }
+}
+CrossController.toString = () => '[class CrossController]';
+
+module.exports = CrossController;
\ No newline at end of file
diff --git a/electron/controller/effect.js b/electron/controller/effect.js
new file mode 100644
index 0000000..026812a
--- /dev/null
+++ b/electron/controller/effect.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const { dialog } = require('electron');
+const { getMainWindow } = require('ee-core/electron');
+
+/**
+ * effect - demo
+ * @class
+ */
+class EffectController {
+
+ /**
+ * select file
+ */
+ selectFile() {
+ const filePaths = dialog.showOpenDialogSync({
+ properties: ['openFile']
+ });
+
+ if (!filePaths) {
+ return null
+ }
+
+ return filePaths[0];
+ }
+
+ /**
+ * login window
+ */
+ loginWindow(args) {
+ 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) {
+ 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]';
+
+module.exports = EffectController;
\ No newline at end of file
diff --git a/electron/controller/example.js b/electron/controller/example.js
new file mode 100644
index 0000000..61e1afd
--- /dev/null
+++ b/electron/controller/example.js
@@ -0,0 +1,18 @@
+'use strict';
+
+/**
+ * example
+ * @class
+ */
+class ExampleController {
+
+ /**
+ * test
+ */
+ async test () {
+ return 'hello electron-egg';
+ }
+}
+ExampleController.toString = () => '[class ExampleController]';
+
+module.exports = ExampleController;
\ No newline at end of file
diff --git a/electron/controller/framework.js b/electron/controller/framework.js
new file mode 100644
index 0000000..2af8f8e
--- /dev/null
+++ b/electron/controller/framework.js
@@ -0,0 +1,270 @@
+'use strict';
+
+const dayjs = require('dayjs');
+const path = require('path');
+const fs = require('fs');
+const { exec } = require('child_process');
+const { app: electronApp, shell } = require('electron');
+const { getExtraResourcesDir } = require('ee-core/ps');
+const { logger } = require('ee-core/log');
+const { getConfig } = require('ee-core/config');
+const { frameworkService } = require('../service/framework');
+const { sqlitedbService } = require('../service/database/sqlitedb');
+const { autoUpdaterService } = require('../service/os/auto_updater');
+
+/**
+ * framework - demo
+ * @class
+ */
+class FrameworkController {
+
+ /**
+ * 所有方法接收两个参数
+ * @param args 前端传的参数
+ * @param event - ipc通信时才有值。详情见:控制器文档
+ */
+
+ /**
+ * sqlite数据库操作
+ */
+ async sqlitedbOperation(args) {
+ const { action, info, delete_name, update_name, update_age, search_age, data_dir } = args;
+
+ const data = {
+ action,
+ result: null,
+ all_list: [],
+ code: 0
+ };
+
+ try {
+ // test
+ sqlitedbService.getDataDir();
+ } catch (err) {
+ console.log(err);
+ data.code = -1;
+ return data;
+ }
+
+ switch (action) {
+ case 'add' :
+ data.result = await sqlitedbService.addTestDataSqlite(info);;
+ break;
+ case 'del' :
+ data.result = await sqlitedbService.delTestDataSqlite(delete_name);;
+ break;
+ case 'update' :
+ data.result = await sqlitedbService.updateTestDataSqlite(update_name, update_age);
+ break;
+ case 'get' :
+ data.result = await sqlitedbService.getTestDataSqlite(search_age);
+ break;
+ case 'getDataDir' :
+ data.result = await sqlitedbService.getDataDir();
+ break;
+ case 'setDataDir' :
+ data.result = await sqlitedbService.setCustomDataDir(data_dir);
+ break;
+ }
+
+ data.all_list = await sqlitedbService.getAllTestDataSqlite();
+
+ return data;
+ }
+
+ /**
+ * 调用其它程序(exe、bash等可执行程序)
+ *
+ */
+ openSoftware(args) {
+ const { softName } = args;
+ const softwarePath = path.join(getExtraResourcesDir(), softName);
+ logger.info('[openSoftware] softwarePath:', softwarePath);
+
+ // 检查程序是否存在
+ if (!fs.existsSync(softwarePath)) {
+ return false;
+ }
+ // 命令行字符串 并 执行, start 命令后面的路径要加双引号
+ const cmdStr = `start "${softwarePath}"`;
+ exec(cmdStr);
+
+ // 方法二
+ // 使用cross模块
+
+ return true;
+ }
+
+ /**
+ * 检测http服务是否开启
+ */
+ async checkHttpServer() {
+ const { enable, protocol, host, port } = getConfig().httpServer;
+ const url = protocol + host + ':' + port;
+ console.log('[checkHttpServer] url:', url);
+ const data = {
+ enable: enable,
+ server: url
+ }
+ return data;
+ }
+
+ /**
+ * 一个 http 请求
+ * args 是 前端传的参数
+ * ctx 是 koa 的 ctx 对象
+ */
+ async doHttpRequest(args, ctx) {
+ const httpInfo = {
+ args,
+ method: ctx.request.method,
+ query: ctx.request.query,
+ body: ctx.request.body
+ }
+ logger.info('httpInfo:', httpInfo);
+
+ const { id } = args;
+ if (!id) {
+ return false;
+ }
+ const dir = electronApp.getPath(id);
+ shell.openPath(dir);
+
+ return true;
+ }
+
+ /**
+ * 一个socket io请求访问此方法
+ */
+ async doSocketRequest(args) {
+ const { id } = args;
+ if (!id) {
+ return false;
+ }
+ const dir = electronApp.getPath(id);
+ shell.openPath(dir);
+
+ return true;
+ }
+
+ /**
+ * 异步消息类型
+ */
+ async ipcInvokeMsg(args) {
+ let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss');
+ const data = args + ' - ' + timeNow;
+
+ return data;
+ }
+
+ /**
+ * 同步消息类型
+ */
+ async ipcSendSyncMsg(args) {
+ let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss');
+ const data = args + ' - ' + timeNow;
+
+ return data;
+ }
+
+ /**
+ * 双向异步通信
+ */
+ ipcSendMsg(args, event) {
+ const { type, content } = args;
+ const data = frameworkService.bothWayMessage(type, content, event);
+
+ return data;
+ }
+
+ /**
+ * 任务
+ */
+ someJob(args, event) {
+ const { jobId, action} = args;
+ let result;
+
+ switch (action) {
+ case 'create':
+ result = frameworkService.doJob(jobId, action, event);
+ break;
+ case 'close':
+ frameworkService.doJob(jobId, action, event);
+ break;
+ case 'pause':
+ frameworkService.doJob(jobId, action, event);
+ break;
+ case 'resume':
+ frameworkService.doJob(jobId, action, event);
+ break;
+ default:
+ }
+
+ let data = {
+ jobId,
+ action,
+ result
+ }
+ return data;
+ }
+
+ /**
+ * 创建任务池
+ */
+ async createPool(args, event) {
+ let num = args.number;
+ frameworkService.doCreatePool(num, event);
+
+ // test monitor
+ frameworkService.monitorJob();
+
+ return;
+ }
+
+ /**
+ * 通过进程池执行任务
+ */
+ someJobByPool(args, event) {
+ const { jobId, action } = args;
+ let result;
+ switch (action) {
+ case 'run':
+ result = frameworkService.doJobByPool(jobId, action, event);
+ break;
+ default:
+ }
+
+ let data = {
+ jobId,
+ action,
+ result
+ }
+ return data;
+ }
+
+ /**
+ * 检查是否有新版本
+ */
+ checkForUpdater() {
+ autoUpdaterService.checkUpdate();
+ return;
+ }
+
+ /**
+ * 下载新版本
+ */
+ downloadApp() {
+ autoUpdaterService.download();
+ return;
+ }
+
+ /**
+ * 测试接口
+ */
+ hello(args) {
+ logger.info('hello ', args);
+ }
+}
+FrameworkController.toString = () => '[class FrameworkController]';
+
+module.exports = FrameworkController;
\ No newline at end of file
diff --git a/electron/controller/os.js b/electron/controller/os.js
new file mode 100644
index 0000000..e49d935
--- /dev/null
+++ b/electron/controller/os.js
@@ -0,0 +1,175 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const {
+ app: electronApp, dialog, shell, Notification,
+} = require('electron');
+const { windowService } = require('../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() {
+ 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() {
+ 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 null
+ }
+
+ return filePaths[0];
+ }
+
+ /**
+ * open directory
+ */
+ openDirectory(args) {
+ 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() {
+ 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) {
+ const wcid = windowService.createWindow(args);
+ return wcid;
+ }
+
+ /**
+ * Get Window contents id
+ */
+ getWCid(args) {
+ const wcid = windowService.getWCid(args);
+ return wcid;
+ }
+
+ /**
+ * Realize communication between two windows through the transfer of the main process
+ */
+ window1ToWindow2(args, event) {
+ windowService.communicate(args, event);
+ return;
+ }
+
+ /**
+ * Realize communication between two windows through the transfer of the main process
+ */
+ window2ToWindow1(args, event) {
+ windowService.communicate(args, event);
+ return;
+ }
+
+ /**
+ * Create system notifications
+ */
+ sendNotification(args, event) {
+ const { title, subtitle, body, silent} = args;
+
+ if (!Notification.isSupported()) {
+ return '当前系统不支持通知';
+ }
+
+ let options = {};
+ if (!title) {
+ options.title = title;
+ }
+ if (!subtitle) {
+ options.subtitle = subtitle;
+ }
+ if (!body) {
+ options.body = body;
+ }
+ if (!silent) {
+ options.silent = silent;
+ }
+ windowService.createNotification(options, event);
+
+ return true
+ }
+}
+OsController.toString = () => '[class OsController]';
+
+module.exports = OsController;
\ No newline at end of file
diff --git a/electron/jobs/example/hello.js b/electron/jobs/example/hello.js
new file mode 100644
index 0000000..a4b0250
--- /dev/null
+++ b/electron/jobs/example/hello.js
@@ -0,0 +1,11 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+
+function welcome() {
+ logger.info('[child-process] [jobs/example/hello] welcome ! ');
+}
+
+module.exports = {
+ welcome
+};
\ No newline at end of file
diff --git a/electron/jobs/example/timer.js b/electron/jobs/example/timer.js
new file mode 100644
index 0000000..ed70364
--- /dev/null
+++ b/electron/jobs/example/timer.js
@@ -0,0 +1,88 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+const { isChildJob, exit } = require('ee-core/ps');
+const { childMessage } = require('ee-core/message');
+const { welcome } = require('./hello');
+const { UserService } = require('../../service/job/user');
+
+/**
+ * example - TimerJob
+ * @class
+ */
+class TimerJob {
+
+ constructor(params) {
+ this.params = params;
+ this.timer = undefined;
+ this.timeoutTimer = undefined;
+ this.number = 0;
+ this.countdown = 10; // 倒计时
+ }
+
+ /**
+ * handle()方法是必要的,且会被自动调用
+ */
+ async handle () {
+ logger.info("[child-process] TimerJob params: ", this.params);
+ const { jobId } = this.params;
+
+ // 子进程中使用service
+ // 1. 确保引入的 service 中不能有electron 的 api或依赖, electron 不支持
+ const userService = new UserService();
+ userService.hello('job');
+
+ // 执行任务
+ this.doTimer(jobId);
+ }
+
+ /**
+ * 暂停任务运行
+ */
+ async pause(jobId) {
+ logger.info("[child-process] Pause timerJob, jobId: ", jobId);
+ clearInterval(this.timer);
+ clearInterval(this.timeoutTimer);
+ }
+
+ /**
+ * 恢复任务运行
+ */
+ async resume(jobId, pid) {
+ logger.info("[child-process] Resume timerJob, jobId: ", jobId, ", pid: ", pid);
+ this.doTimer(jobId);
+ }
+
+ /**
+ * 运行任务
+ */
+ async doTimer(jobId) {
+ // 计时器模拟任务
+ const eventName = 'job-timer-progress-' + jobId;
+ this.timer = setInterval(() => {
+ welcome();
+
+ childMessage.send(eventName, {jobId, number: this.number, end: false});
+ this.number++;
+ this.countdown--;
+ }, 1000);
+
+ // 用 setTimeout 模拟任务运行时长
+ this.timeoutTimer = setTimeout(() => {
+ // 关闭计时器模拟任务
+ clearInterval(this.timer);
+
+ // 任务结束,重置前端显示
+ childMessage.send(eventName, {jobId, number:0, pid:0, end: true});
+
+ // 如果是childJob任务,必须调用 exit() 方法,让进程退出,否则会常驻内存
+ // 如果是childPoolJob任务,常驻内存,等待下一个业务
+ if (isChildJob()) {
+ exit();
+ }
+ }, this.countdown * 1000)
+ }
+}
+
+TimerJob.toString = () => '[class TimerJob]';
+module.exports = TimerJob;
diff --git a/electron/main.js b/electron/main.js
new file mode 100644
index 0000000..b005896
--- /dev/null
+++ b/electron/main.js
@@ -0,0 +1,19 @@
+const { ElectronEgg } = require('ee-core');
+const { Lifecycle } = require('./preload/lifecycle');
+const { preload } = require('./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();
\ No newline at end of file
diff --git a/electron/preload/bridge.js b/electron/preload/bridge.js
new file mode 100644
index 0000000..c9b86db
--- /dev/null
+++ b/electron/preload/bridge.js
@@ -0,0 +1,10 @@
+/*
+ * 如果启用了上下文隔离,渲染进程无法使用electron的api,
+ * 可通过contextBridge 导出api给渲染进程使用
+ */
+
+const { contextBridge, ipcRenderer } = require('electron')
+
+contextBridge.exposeInMainWorld('electron', {
+ ipcRenderer: ipcRenderer,
+})
\ No newline at end of file
diff --git a/electron/preload/index.js b/electron/preload/index.js
new file mode 100644
index 0000000..6794b52
--- /dev/null
+++ b/electron/preload/index.js
@@ -0,0 +1,23 @@
+/*************************************************
+ ** preload为预加载模块,该文件将会在程序启动时加载 **
+ *************************************************/
+
+const { logger } = require('ee-core/log');
+const { trayService } = require('../service/os/tray');
+const { securityService } = require('../service/os/security');
+const { autoUpdaterService } = require('../service/os/auto_updater');
+
+function preload() {
+ // 示例功能模块,可选择性使用和修改
+ logger.info('[preload] load 5');
+ trayService.create();
+ securityService.create();
+ autoUpdaterService.create();
+}
+
+/**
+* 预加载模块入口
+*/
+module.exports = {
+ preload
+}
\ No newline at end of file
diff --git a/electron/preload/lifecycle.js b/electron/preload/lifecycle.js
new file mode 100644
index 0000000..a18a2cc
--- /dev/null
+++ b/electron/preload/lifecycle.js
@@ -0,0 +1,50 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+const { getConfig } = require('ee-core/config');
+const { getMainWindow } = require('ee-core/electron');
+
+class Lifecycle {
+
+ /**
+ * core app have been loaded
+ */
+ async ready() {
+ logger.info('[lifecycle] ready');
+ }
+
+ /**
+ * electron app ready
+ */
+ async electronAppReady() {
+ logger.info('[lifecycle] electron-app-ready');
+ }
+
+ /**
+ * main window have been loaded
+ */
+ async windowReady() {
+ logger.info('[lifecycle] window-ready');
+ // 延迟加载,无白屏
+ const { windowsOption } = getConfig();
+ if (windowsOption.show == false) {
+ const win = getMainWindow();
+ win.once('ready-to-show', () => {
+ win.show();
+ win.focus();
+ })
+ }
+ }
+
+ /**
+ * before app close
+ */
+ async beforeClose() {
+ logger.info('[lifecycle] before-close');
+ }
+}
+Lifecycle.toString = () => '[class Lifecycle]';
+
+module.exports = {
+ Lifecycle
+};
\ No newline at end of file
diff --git a/electron/service/cross.js b/electron/service/cross.js
new file mode 100644
index 0000000..3e41faa
--- /dev/null
+++ b/electron/service/cross.js
@@ -0,0 +1,151 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+const { getExtraResourcesDir, getLogDir } = require('ee-core/ps');
+const path = require("path");
+const axios = require('axios');
+const { is } = require('ee-core/utils');
+const { cross } = require('ee-core/cross');
+
+/**
+ * cross
+ * @class
+ */
+class CrossService {
+
+ info() {
+ 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) {
+ const serverUrl = cross.getUrl(name);
+ return serverUrl;
+ }
+
+ killServer(type, name) {
+ 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() {
+ // 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());
+
+ return;
+ }
+
+ /**
+ * create java server
+ */
+ async createJavaServer() {
+ 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));
+
+ return;
+ }
+
+ /**
+ * 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() {
+ // 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());
+
+ return;
+ }
+
+ async requestApi(name, urlPath, params) {
+ 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]';
+
+module.exports = {
+ CrossService,
+ crossService: new CrossService()
+};
\ No newline at end of file
diff --git a/electron/service/database/basedb.js b/electron/service/database/basedb.js
new file mode 100644
index 0000000..4cd0815
--- /dev/null
+++ b/electron/service/database/basedb.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const { SqliteStorage } = require('ee-core/storage');
+const { getDataDir } = require('ee-core/ps');
+const path = require('path');
+
+/**
+ * sqlite数据存储
+ * @class
+ */
+class BasedbService {
+
+ constructor(options) {
+ const { dbname } = options;
+ this.dbname = dbname;
+ this.db = undefined;
+ this._init();
+ }
+
+ /*
+ * 初始化
+ */
+ _init() {
+ // 定义数据文件
+ const dbFile = path.join(getDataDir(), "db", this.dbname);
+ const sqliteOptions = {
+ timeout: 6000,
+ verbose: console.log
+ }
+ this.storage = new SqliteStorage(dbFile, sqliteOptions);
+ this.db = this.storage.db;
+ }
+
+ /*
+ * change data dir (sqlite)
+ */
+ changeDataDir(dir) {
+ // the absolute path of the db file
+ const dbFile = path.join(dir, this.dbname);
+ const sqliteOptions = {
+ timeout: 6000,
+ verbose: console.log
+ }
+ this.storage = new SqliteStorage(dbFile, sqliteOptions);
+ this.db = this.storage.db;
+ }
+}
+BasedbService.toString = () => '[class BasedbService]';
+
+module.exports = {
+ BasedbService,
+};
\ No newline at end of file
diff --git a/electron/service/database/sqlitedb.js b/electron/service/database/sqlitedb.js
new file mode 100644
index 0000000..35bcff4
--- /dev/null
+++ b/electron/service/database/sqlitedb.js
@@ -0,0 +1,111 @@
+'use strict';
+
+const { BasedbService } = require('./basedb');
+
+/**
+ * sqlite数据存储
+ * @class
+ */
+class SqlitedbService extends BasedbService {
+
+ constructor () {
+ const options = {
+ dbname: 'sqlite-demo.db',
+ }
+ super(options);
+ this.userTableName = 'user';
+ this._initTable();
+ }
+
+ /*
+ * 初始化表
+ */
+ _initTable() {
+ // 检查表是否存在
+ const masterStmt = this.db.prepare('SELECT * FROM sqlite_master WHERE type=? AND name = ?');
+ let tableExists = masterStmt.get('table', this.userTableName);
+ if (!tableExists) {
+ // 创建表
+ const create_user_table_sql =
+ `CREATE TABLE ${this.userTableName}
+ (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name CHAR(50) NOT NULL,
+ age INT
+ );`
+ this.db.exec(create_user_table_sql);
+ }
+ }
+
+ /*
+ * 增 Test data (sqlite)
+ */
+ async addTestDataSqlite(data) {
+ const insert = this.db.prepare(`INSERT INTO ${this.userTableName} (name, age) VALUES (@name, @age)`);
+ insert.run(data);
+ return true;
+ }
+
+ /*
+ * 删 Test data (sqlite)
+ */
+ async delTestDataSqlite(name = '') {
+ const delUser = this.db.prepare(`DELETE FROM ${this.userTableName} WHERE name = ?`);
+ delUser.run(name);
+ return true;
+ }
+
+ /*
+ * 改 Test data (sqlite)
+ */
+ async updateTestDataSqlite(name= '', age = 0) {
+ const updateUser = this.db.prepare(`UPDATE ${this.userTableName} SET age = ? WHERE name = ?`);
+ updateUser.run(age, name);
+ return true;
+ }
+
+ /*
+ * 查 Test data (sqlite)
+ */
+ async getTestDataSqlite(age = 0) {
+ const selectUser = this.db.prepare(`SELECT * FROM ${this.userTableName} WHERE age = @age`);
+ const users = selectUser.all({age: age});
+ return users;
+ }
+
+ /*
+ * all Test data (sqlite)
+ */
+ async getAllTestDataSqlite() {
+ const selectAllUser = this.db.prepare(`SELECT * FROM ${this.userTableName} `);
+ const allUser = selectAllUser.all();
+ return allUser;
+ }
+
+ /*
+ * get data dir (sqlite)
+ */
+ async getDataDir() {
+ const dir = this.storage.getDbDir();
+ return dir;
+ }
+
+ /*
+ * set custom data dir (sqlite)
+ */
+ async setCustomDataDir(dir) {
+ if (!dir) {
+ return;
+ }
+
+ this.changeDataDir(dir);
+ this._initTable();
+ return;
+ }
+}
+SqlitedbService.toString = () => '[class SqlitedbService]';
+
+module.exports = {
+ SqlitedbService,
+ sqlitedbService: new SqlitedbService()
+};
diff --git a/electron/service/effect.js b/electron/service/effect.js
new file mode 100644
index 0000000..3d8627d
--- /dev/null
+++ b/electron/service/effect.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+
+/**
+ * effect
+ * @class
+ */
+class EffectService {
+
+ /**
+ * hello
+ */
+ async hello(args) {
+ let obj = {
+ status:'ok',
+ params: args
+ }
+ logger.info('EffectService obj:', obj);
+
+ return obj;
+ }
+
+}
+EffectService.toString = () => '[class EffectService]';
+
+module.exports = {
+ EffectService,
+ effectService: new EffectService()
+};
\ No newline at end of file
diff --git a/electron/service/example.js b/electron/service/example.js
new file mode 100644
index 0000000..cf55fb6
--- /dev/null
+++ b/electron/service/example.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+
+/**
+ * 示例服务
+ * @class
+ */
+class ExampleService {
+
+ /**
+ * test
+ */
+ async test(args) {
+ let obj = {
+ status:'ok',
+ params: args
+ }
+
+ logger.info('ExampleService obj:', obj);
+
+ return obj;
+ }
+}
+ExampleService.toString = () => '[class ExampleService]';
+
+module.exports = {
+ ExampleService,
+ exampleService: new ExampleService()
+};
\ No newline at end of file
diff --git a/electron/service/framework.js b/electron/service/framework.js
new file mode 100644
index 0000000..7b98f5b
--- /dev/null
+++ b/electron/service/framework.js
@@ -0,0 +1,163 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+const { ChildJob, ChildPoolJob } = require('ee-core/jobs');
+
+/**
+ * framework
+ * @class
+ */
+class FrameworkService {
+
+ constructor() {
+ // 在构造函数中初始化一些变量
+ this.myTimer = null;
+ this.myJob = new ChildJob();
+ this.myJobPool = new ChildPoolJob();
+ this.taskForJob = {};
+ }
+
+ /**
+ * test
+ */
+ async test(args) {
+ let obj = {
+ status:'ok',
+ params: args
+ }
+ logger.info('FrameworkService obj:', obj);
+ return obj;
+ }
+
+ /**
+ * ipc通信(双向)
+ */
+ bothWayMessage(type, content, event) {
+ // 前端ipc频道 channel
+ const channel = 'controller/framework/ipcSendMsg';
+
+ if (type == 'start') {
+ // 每隔1秒,向前端页面发送消息
+ // 用定时器模拟
+ this.myTimer = setInterval(function(e, c, msg) {
+ let timeNow = Date.now();
+ let data = msg + ':' + timeNow;
+ e.reply(`${c}`, data)
+ }, 1000, event, channel, content)
+
+ return '开始了'
+ } else if (type == 'end') {
+ clearInterval(this.myTimer);
+ return '停止了'
+ } else {
+ return 'ohther'
+ }
+ }
+
+ /**
+ * 执行任务
+ */
+ doJob(jobId, action, event) {
+ let res = {};
+ let oneTask;
+ const channel = 'controller/framework/timerJobProgress';
+ if (action == 'create') {
+ // 执行任务及监听进度
+ let eventName = 'job-timer-progress-' + jobId;
+ const timerTask = this.myJob.exec('./jobs/example/timer', {jobId});
+ timerTask.emitter.on(eventName, (data) => {
+ logger.info('[main-process] timerTask, from TimerJob data:', data);
+ // 发送数据到渲染进程
+ event.sender.send(`${channel}`, data)
+ })
+
+ // 执行任务及监听进度 异步
+ // myjob.execPromise('./jobs/example/timer', {jobId}).then(task => {
+ // task.emitter.on(eventName, (data) => {
+ // Log.info('[main-process] timerTask, from TimerJob data:', data);
+ // // 发送数据到渲染进程
+ // event.sender.send(`${channel}`, data)
+ // })
+ // });
+
+ res.pid = timerTask.pid;
+ this.taskForJob[jobId] = timerTask;
+ }
+ if (action == 'close') {
+ oneTask = this.taskForJob[jobId];
+ oneTask.kill();
+ event.sender.send(`${channel}`, {jobId, number:0, pid:0});
+ }
+ if (action == 'pause') {
+ oneTask = this.taskForJob[jobId];
+ oneTask.callFunc('./jobs/example/timer', 'pause', jobId);
+ }
+ if (action == 'resume') {
+ oneTask = this.taskForJob[jobId];
+ oneTask.callFunc('./jobs/example/timer', 'resume', jobId, oneTask.pid);
+ }
+
+ return res;
+ }
+
+
+
+ /**
+ * 创建pool
+ */
+ doCreatePool(num, event) {
+ const channel = 'controller/framework/createPoolNotice';
+ this.myJobPool.create(num).then(pids => {
+ event.reply(`${channel}`, pids);
+ });
+ }
+
+ /**
+ * 通过进程池执行任务
+ */
+ doJobByPool(jobId, action, event) {
+ let res = {};
+ const channel = 'controller/framework/timerJobProgress';
+ if (action == 'run') {
+ // 异步-执行任务及监听进度
+ this.myJobPool.runPromise('./jobs/example/timer', {jobId}).then(task => {
+
+ // 监听器名称唯一,否则会出现重复监听。
+ // 任务完成时,需要移除监听器,防止内存泄漏
+ let eventName = 'job-timer-progress-' + jobId;
+ task.emitter.on(eventName, (data) => {
+ logger.info('[main-process] [ChildPoolJob] timerTask, from TimerJob data:', data);
+
+ // 发送数据到渲染进程
+ event.sender.send(`${channel}`, data)
+
+ // 如果收到任务完成的消息,移除监听器
+ if (data.end) {
+ task.emitter.removeAllListeners(eventName);
+ }
+ });
+
+ res.pid = task.pid;
+ });
+ }
+ return res;
+ }
+
+ /**
+ * 获取正在运行的 job 进程
+ */
+ monitorJob() {
+ setInterval(() => {
+ let jobPids = this.myJob.getPids();
+ let jobPoolPids = this.myJobPool.getPids();
+ logger.info(`[main-process] [monitorJob] jobPids: ${jobPids}, jobPoolPids: ${jobPoolPids}`);
+ }, 5000)
+ }
+
+}
+FrameworkService.toString = () => '[class FrameworkService]';
+
+module.exports = {
+ FrameworkService,
+ frameworkService: new FrameworkService()
+};
\ No newline at end of file
diff --git a/electron/service/job/user.js b/electron/service/job/user.js
new file mode 100644
index 0000000..991c0f9
--- /dev/null
+++ b/electron/service/job/user.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+
+// The service used in the job should not rely on Electron's API, as it may cause errors
+class UserService {
+
+ /**
+ * hello
+ */
+ async hello(args) {
+ let obj = {
+ status:'ok',
+ params: args
+ }
+ logger.info('UserService obj:', obj);
+
+ return obj;
+ }
+
+}
+UserService.toString = () => '[class UserService]';
+
+module.exports = {
+ UserService
+};
\ No newline at end of file
diff --git a/electron/service/os/auto_updater.js b/electron/service/os/auto_updater.js
new file mode 100644
index 0000000..6ec739c
--- /dev/null
+++ b/electron/service/os/auto_updater.js
@@ -0,0 +1,168 @@
+const { app: electronApp } = require('electron');
+const { autoUpdater } = require("electron-updater");
+const { is } = require('ee-core/utils');
+const { logger } = require('ee-core/log');
+const { getMainWindow, setCloseAndQuit } = require('ee-core/electron');
+
+/**
+ * 自动升级
+ * @class
+ */
+class AutoUpdaterService {
+ constructor() {
+ this.config = {
+ windows: false,
+ macOS: false,
+ linux: false,
+ options: {
+ provider: 'generic',
+ url: 'http://kodo.qiniu.com/'
+ },
+ }
+ }
+
+ /**
+ * 创建
+ */
+ create () {
+ 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);
+
+ // 设置下载服务器地址
+ let server = cfg.options.url;
+ let 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 = parseInt(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('[autoUpdater] progress: ', text);
+ this.sendStatusToWindow(data);
+ })
+ autoUpdater.on('update-downloaded', () => {
+ const data = {
+ status: status.downloaded,
+ desc: '下载完成'
+ }
+ this.sendStatusToWindow(data);
+
+ // 托盘插件里面设置了阻止窗口关闭,这里设置允许关闭窗口
+ setCloseAndQuit(true);
+
+ // Install updates and exit the application
+ autoUpdater.quitAndInstall();
+ });
+ }
+
+ /**
+ * 检查更新
+ */
+ checkUpdate () {
+ autoUpdater.checkForUpdates();
+ }
+
+ /**
+ * 下载更新
+ */
+ download () {
+ autoUpdater.downloadUpdate();
+ }
+
+ /**
+ * 向前端发消息
+ */
+ sendStatusToWindow(content = {}) {
+ const textJson = JSON.stringify(content);
+ const channel = 'custom/app/updater';
+ const win = getMainWindow();
+ win.webContents.send(channel, textJson);
+ }
+
+ /**
+ * 单位转换
+ */
+ bytesChange (limit) {
+ 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]';
+
+module.exports = {
+ autoUpdaterService: new AutoUpdaterService()
+};
\ No newline at end of file
diff --git a/electron/service/os/security.js b/electron/service/os/security.js
new file mode 100644
index 0000000..002f9d0
--- /dev/null
+++ b/electron/service/os/security.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const { logger } = require('ee-core/log');
+const { app: electronApp } = require('electron');
+
+/**
+ * 安全
+ * @class
+ */
+class SecurityService {
+
+ /**
+ * 创建
+ */
+ create () {
+ logger.info('[security] load');
+ const runWithDebug = process.argv.find(function(e){
+ let isHasDebug = e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port");
+ return isHasDebug;
+ })
+
+ // 不允许远程调试
+ if (runWithDebug) {
+ logger.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug);
+ electronApp.quit();
+ }
+ }
+}
+SecurityService.toString = () => '[class SecurityService]';
+
+module.exports = {
+ securityService: new SecurityService()
+};
\ No newline at end of file
diff --git a/electron/service/os/tray.js b/electron/service/os/tray.js
new file mode 100644
index 0000000..39bed02
--- /dev/null
+++ b/electron/service/os/tray.js
@@ -0,0 +1,75 @@
+const { Tray, Menu } = require('electron');
+const path = require('path');
+const { getBaseDir } = require('ee-core/ps');
+const { logger } = require('ee-core/log');
+const { app: electronApp } = require('electron');
+const { getMainWindow, getCloseAndQuit, setCloseAndQuit } = require('ee-core/electron');
+
+/**
+ * 托盘
+ * @class
+ */
+class TrayService {
+
+ constructor() {
+ this.tray = null;
+ this.config = {
+ title: 'electron-egg',
+ icon: '/public/images/tray.png'
+ }
+ }
+
+ /**
+ * 创建托盘
+ */
+ create () {
+ logger.info('[tray] load');
+
+ const cfg = this.config;
+ const mainWindow = getMainWindow();
+
+ // tray icon
+ const iconPath = path.join(getBaseDir(), cfg.icon);
+
+ // 托盘菜单功能列表
+ const trayMenuTemplate = [
+ {
+ label: '显示',
+ click: function () {
+ mainWindow.show();
+ }
+ },
+ {
+ label: '退出',
+ click: function () {
+ electronApp.quit();
+ }
+ }
+ ]
+
+ // 设置一个标识,点击关闭,最小化到托盘
+ setCloseAndQuit(false);
+ mainWindow.on('close', (event) => {
+ if (getCloseAndQuit()) {
+ return;
+ }
+ mainWindow.hide();
+ event.preventDefault();
+ });
+
+ // 实例化托盘
+ this.tray = new Tray(iconPath);
+ this.tray.setToolTip(cfg.title);
+ const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
+ this.tray.setContextMenu(contextMenu);
+ // 左键单击的时候能够显示主窗口
+ this.tray.on('click', () => {
+ mainWindow.show()
+ })
+ }
+}
+TrayService.toString = () => '[class TrayService]';
+
+module.exports = {
+ trayService: new TrayService()
+};
\ No newline at end of file
diff --git a/electron/service/os/window.js b/electron/service/os/window.js
new file mode 100644
index 0000000..c00c710
--- /dev/null
+++ b/electron/service/os/window.js
@@ -0,0 +1,134 @@
+'use strict';
+
+const path = require('path');
+const { BrowserWindow, Notification } = require('electron');
+const { getMainWindow } = require('ee-core/electron');
+const { isProd, getBaseDir } = require('ee-core/ps');
+const { getConfig } = require('ee-core/config');
+const { isFileProtocol } = require('ee-core/utils');
+const { logger } = require('ee-core/log');
+
+/**
+ * Window
+ * @class
+ */
+class WindowService {
+
+ constructor() {
+ this.myNotification = null;
+ this.windows = {}
+ }
+
+ /**
+ * Create a new window
+ */
+ createWindow(args) {
+ const { type, content, windowName, windowTitle } = args;
+ let contentUrl = null;
+ 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 (isFileProtocol(mainServer.protocol)) {
+ addr = mainServer.protocol + path.join(getBaseDir(), mainServer.indexPath);
+ } else {
+ addr = mainServer.protocol + mainServer.host + ':' + mainServer.port;
+ }
+ }
+
+ contentUrl = addr + content;
+ } else {
+ // some
+ }
+
+ 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) {
+ const { windowName } = args;
+ let win;
+ 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) {
+ 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, event) {
+ const channel = 'controller/os/sendNotification';
+ this.myNotification = new Notification(options);
+
+ if (options.clickEvent) {
+ this.myNotification.on('click', (e) => {
+ let data = {
+ type: 'click',
+ msg: '您点击了通知消息'
+ }
+ event.reply(`${channel}`, data)
+ });
+ }
+
+ if (options.closeEvent) {
+ this.myNotification.on('close', (e) => {
+ let data = {
+ type: 'close',
+ msg: '您关闭了通知消息'
+ }
+ event.reply(`${channel}`, data)
+ });
+ }
+
+ this.myNotification.show();
+ }
+
+}
+WindowService.toString = () => '[class WindowService]';
+
+module.exports = {
+ WindowService,
+ windowService: new WindowService()
+};
\ No newline at end of file
diff --git a/frontend/.editorconfig b/frontend/.editorconfig
new file mode 100644
index 0000000..3454886
--- /dev/null
+++ b/frontend/.editorconfig
@@ -0,0 +1,14 @@
+# https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false
diff --git a/frontend/.env.development b/frontend/.env.development
new file mode 100644
index 0000000..9005571
--- /dev/null
+++ b/frontend/.env.development
@@ -0,0 +1,2 @@
+VITE_TITLE=""
+VITE_GO_URL="http://localhost:8081"
\ No newline at end of file
diff --git a/frontend/.env.production b/frontend/.env.production
new file mode 100644
index 0000000..a0eaf9f
--- /dev/null
+++ b/frontend/.env.production
@@ -0,0 +1,2 @@
+VITE_TITLE=""
+VITE_GO_URL="http://www.test.com"
\ No newline at end of file
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..082d756
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local
+package-lock.json
\ No newline at end of file
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..c0b96bd
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json
new file mode 100644
index 0000000..ee3163b
--- /dev/null
+++ b/frontend/jsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ // "allowSyntheticDefaultImports": true,
+ "baseUrl": "./",
+ "paths": {
+ "@/*": [
+ "src/*"
+ ],
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..764f728
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "ee",
+ "version": "4.0.0",
+ "scripts": {
+ "dev": "vite --host --port 8080",
+ "serve": "vite --host --port 8080",
+ "build-staging": "vite build --mode staging",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@ant-design/icons-vue": "^6.1.0",
+ "ant-design-vue": "^3.2.20",
+ "axios": "^0.21.1",
+ "pinia": "^2.2.6",
+ "socket.io-client": "^4.4.1",
+ "store2": "^2.13.2",
+ "vue": "^3.5.12",
+ "vue-router": "^4.0.14",
+ "vuex": "^4.0.2"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.2.3",
+ "@vue/compiler-sfc": "^3.2.33",
+ "less": "^4.1.2",
+ "less-loader": "^10.2.0",
+ "postcss": "^8.4.13",
+ "postcss-pxtorem": "^6.0.0",
+ "terser": "^5.19.1",
+ "vite": "^5.4.11",
+ "vite-plugin-compression": "^0.5.1"
+ }
+}
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
new file mode 100644
index 0000000..f9684e6
--- /dev/null
+++ b/frontend/src/App.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
new file mode 100644
index 0000000..73a707c
--- /dev/null
+++ b/frontend/src/api/index.js
@@ -0,0 +1,85 @@
+
+/**
+ * Definition of communication channel between main process and rendering process
+ * separator: "/" | "." ; (Please check the config file properties: channelSeparator)
+ * format:controller/filename/method | controller.filename.method
+ * Definition of communication channels between main process and rendering process
+ */
+const ipcApiRoute = {
+ example: {
+ test: 'controller/example/test',
+ },
+ framework: {
+ checkForUpdater: 'controller/framework/checkForUpdater',
+ downloadApp: 'controller/framework/downloadApp',
+ jsondbOperation: 'controller/framework/jsondbOperation',
+ sqlitedbOperation: 'controller/framework/sqlitedbOperation',
+ uploadFile: 'controller/framework/uploadFile',
+ checkHttpServer: 'controller/framework/checkHttpServer',
+ doHttpRequest: 'controller/framework/doHttpRequest',
+ doSocketRequest: 'controller/framework/doSocketRequest',
+ ipcInvokeMsg: 'controller/framework/ipcInvokeMsg',
+ ipcSendSyncMsg: 'controller/framework/ipcSendSyncMsg',
+ ipcSendMsg: 'controller/framework/ipcSendMsg',
+ startJavaServer: 'controller/framework/startJavaServer',
+ closeJavaServer: 'controller/framework/closeJavaServer',
+ someJob: 'controller/framework/someJob',
+ timerJobProgress: 'controller/framework/timerJobProgress',
+ createPool: 'controller/framework/createPool',
+ createPoolNotice: 'controller/framework/createPoolNotice',
+ someJobByPool: 'controller/framework/someJobByPool',
+ hello: 'controller/framework/hello',
+ openSoftware: 'controller/framework/openSoftware',
+ },
+
+ // os
+ os: {
+ messageShow: 'controller/os/messageShow',
+ messageShowConfirm: 'controller/os/messageShowConfirm',
+ selectFolder: 'controller/os/selectFolder',
+ selectPic: 'controller/os/selectPic',
+ openDirectory: 'controller/os/openDirectory',
+ loadViewContent: 'controller/os/loadViewContent',
+ removeViewContent: 'controller/os/removeViewContent',
+ createWindow: 'controller/os/createWindow',
+ getWCid: 'controller/os/getWCid',
+ sendNotification: 'controller/os/sendNotification',
+ initPowerMonitor: 'controller/os/initPowerMonitor',
+ getScreen: 'controller/os/getScreen',
+ autoLaunch: 'controller/os/autoLaunch',
+ setTheme: 'controller/os/setTheme',
+ getTheme: 'controller/os/getTheme',
+ window1ToWindow2: 'controller/os/window1ToWindow2',
+ window2ToWindow1: 'controller/os/window2ToWindow1',
+ },
+
+ // effect
+ effect: {
+ selectFile: 'controller/effect/selectFile',
+ loginWindow: 'controller/effect/loginWindow',
+ restoreWindow: 'controller/effect/restoreWindow',
+ },
+
+ // cross
+ cross: {
+ crossInfo: 'controller/cross/info',
+ getCrossUrl: 'controller/cross/getUrl',
+ killCrossServer: 'controller/cross/killServer',
+ createCrossServer: 'controller/cross/createServer',
+ requestApi: 'controller/cross/requestApi',
+ }
+}
+
+/**
+ * Customize Channel
+ * Format: Custom (recommended to add a prefix)
+ */
+const specialIpcRoute = {
+ appUpdater: 'custom/app/updater', // updater channel
+}
+
+export {
+ ipcApiRoute,
+ specialIpcRoute
+}
+
diff --git a/frontend/src/assets/global.less b/frontend/src/assets/global.less
new file mode 100644
index 0000000..4192318
--- /dev/null
+++ b/frontend/src/assets/global.less
@@ -0,0 +1,16 @@
+#app {
+ font-family: Avenir, Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ text-align: center;
+ color: #2c3e50;
+ height: 100%;
+}
+
+/* 滚动条 */
+::-webkit-scrollbar{width:8px;height:4px}
+::-webkit-scrollbar-button{width:10px;height:0}
+::-webkit-scrollbar-track{background:0 0}
+::-webkit-scrollbar-thumb{background:#E6FFEE;-webkit-transition:.3s;transition:.3s}
+::-webkit-scrollbar-thumb:hover{background-color:#07C160}
+::-webkit-scrollbar-thumb:active{background-color:#07C160}
\ No newline at end of file
diff --git a/frontend/src/assets/login.png b/frontend/src/assets/login.png
new file mode 100644
index 0000000..5882c86
Binary files /dev/null and b/frontend/src/assets/login.png differ
diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png
new file mode 100644
index 0000000..95dc60b
Binary files /dev/null and b/frontend/src/assets/logo.png differ
diff --git a/frontend/src/assets/theme.less b/frontend/src/assets/theme.less
new file mode 100644
index 0000000..8d9b773
--- /dev/null
+++ b/frontend/src/assets/theme.less
@@ -0,0 +1,17 @@
+@import 'ant-design-vue/dist/antd.less';
+
+// 可自定义主题颜色
+@primary-color: #07C160; // 全局主色
+@link-color: #1890ff; // 链接色
+@success-color: #52c41a; // 成功色
+@warning-color: #faad14; // 警告色
+@error-color: #f5222d; // 错误色
+@font-size-base: 14px; // 主字号
+@heading-color: rgba(0, 0, 0, 0.85); // 标题色
+@text-color: rgba(0, 0, 0, 0.65); // 主文本色
+@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
+@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
+@border-radius-base: 4px; // 组件/浮层圆角
+@border-color-base: #dce3e8; // 边框色
+@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影
+
diff --git a/frontend/src/components/global/iconFont.js b/frontend/src/components/global/iconFont.js
new file mode 100644
index 0000000..6ee51f0
--- /dev/null
+++ b/frontend/src/components/global/iconFont.js
@@ -0,0 +1,18 @@
+import { createFromIconfontCN } from '@ant-design/icons-vue'
+import { h } from 'vue'
+
+const IconFont = createFromIconfontCN({
+ scriptUrl: 'https://at.alicdn.com/t/font_2456157_4ovzopz659q.js',
+ extraCommonProps: {
+ type: 'icon-fengche',
+ style: {
+ fontSize: '18px',
+ },
+ },
+})
+
+const DynamicIconFont = props => {
+ return h(IconFont, { type: props.type || 'icon-fengche' })
+}
+
+export default DynamicIconFont
diff --git a/frontend/src/components/global/index.js b/frontend/src/components/global/index.js
new file mode 100644
index 0000000..35e4bf5
--- /dev/null
+++ b/frontend/src/components/global/index.js
@@ -0,0 +1,12 @@
+import iconFont from './iconFont'
+const modules = import.meta.glob('./*.vue', { eager: true })
+const map = {}
+Object.keys(modules).forEach(file => {
+ const modulesName = file.replace('./', '').replace('.vue', '')
+ map[modulesName] = modules[file].default
+})
+const globalComponents = {
+ ...map,
+ iconFont,
+}
+export default globalComponents
diff --git a/frontend/src/layouts/AppSider.vue b/frontend/src/layouts/AppSider.vue
new file mode 100644
index 0000000..a9ff2b4
--- /dev/null
+++ b/frontend/src/layouts/AppSider.vue
@@ -0,0 +1,111 @@
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/layouts/Menu.vue b/frontend/src/layouts/Menu.vue
new file mode 100644
index 0000000..d327c5a
--- /dev/null
+++ b/frontend/src/layouts/Menu.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
diff --git a/frontend/src/layouts/index.js b/frontend/src/layouts/index.js
new file mode 100644
index 0000000..7ae5f46
--- /dev/null
+++ b/frontend/src/layouts/index.js
@@ -0,0 +1,7 @@
+import AppSider from '@/layouts/AppSider'
+import Menu from '@/layouts/Menu'
+
+export {
+ AppSider,
+ Menu
+}
diff --git a/frontend/src/main.js b/frontend/src/main.js
new file mode 100644
index 0000000..908ad1e
--- /dev/null
+++ b/frontend/src/main.js
@@ -0,0 +1,26 @@
+import * as AntIcon from '@ant-design/icons-vue';
+import Antd from 'ant-design-vue';
+import { createApp } from 'vue';
+import App from './App.vue';
+import './assets/global.less';
+import './assets/theme.less';
+import components from './components/global';
+import Router from './router/index';
+
+const app = createApp(App)
+app.config.productionTip = false
+
+// components
+for (const i in components) {
+ app.component(i, components[i])
+}
+
+// icon
+for (const i in AntIcon) {
+ const whiteList = ['createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor', 'default']
+ if (!whiteList.includes(i)) {
+ app.component(i, AntIcon[i])
+ }
+}
+
+app.use(Antd).use(Router).mount('#app')
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
new file mode 100644
index 0000000..e69c70f
--- /dev/null
+++ b/frontend/src/router/index.js
@@ -0,0 +1,9 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import routerMap from './routerMap'
+
+const Router = createRouter({
+ history: createWebHashHistory(),
+ routes: routerMap,
+})
+
+export default Router
diff --git a/frontend/src/router/routerMap.js b/frontend/src/router/routerMap.js
new file mode 100644
index 0000000..3d647d6
--- /dev/null
+++ b/frontend/src/router/routerMap.js
@@ -0,0 +1,142 @@
+/**
+ * 基础路由
+ * @type { *[] }
+ */
+
+const constantRouterMap = [
+ {
+ path: '/',
+ component: () => import('@/layouts/AppSider.vue'),
+ children: [
+ {
+ path: '/framework',
+ name: 'Framework',
+ component: () => import('@/layouts/Menu.vue'),
+ props: { id: 'framework' },
+ //props: true,
+ redirect: { name: 'FrameworkSocketIpc' },
+ children: [
+ {
+ path: '/framework/socket/ipc',
+ name: 'FrameworkSocketIpc',
+ component: () => import('@/views/framework/socket/Ipc.vue')
+ },
+ {
+ path: '/framework/socket/httpserver',
+ name: 'FrameworkSocketHttpServer',
+ component: () => import('@/views/framework/socket/HttpServer.vue')
+ },
+ {
+ path: '/framework/socket/socketserver',
+ name: 'FrameworkSocketSocketServer',
+ component: () => import('@/views/framework/socket/SocketServer.vue')
+ },
+ {
+ path: '/framework/sqlitedb/index',
+ name: 'FrameworkSqliteDBIndex',
+ component: () => import('@/views/framework/sqlitedb/Index.vue')
+ },
+ {
+ path: '/framework/jobs/index',
+ name: 'FrameworkJobsIndex',
+ component: () => import('@/views/framework/jobs/Index.vue')
+ },
+ {
+ path: '/framework/software/index',
+ name: 'FrameworkSoftwareIndex',
+ component: () => import('@/views/framework/software/Index.vue')
+ },
+ {
+ path: '/framework/updater/index',
+ name: 'FrameworkUpdaterIndex',
+ component: () => import('@/views/framework/updater/Index.vue')
+ },
+ ]
+ },
+ {
+ path: '/os',
+ name: 'Os',
+ component: () => import('@/layouts/Menu.vue'),
+ props: { id: 'os' },
+ redirect: { name: 'OsFileIndex' },
+ children: [
+ {
+ path: '/os/file/index',
+ name: 'OsFileIndex',
+ component: () => import('@/views/os/file/Index.vue')
+ },
+ {
+ path: '/os/file/pic',
+ name: 'OsFilePic',
+ component: () => import('@/views/os/file/Pic.vue')
+ },
+ {
+ path: '/os/window/index',
+ name: 'OsWindowIndex',
+ component: () => import('@/views/os/window/Index.vue')
+ },
+ {
+ path: '/os/notification/index',
+ name: 'OsNotificationIndex',
+ component: () => import('@/views/os/notification/Index.vue')
+ }
+ ]
+ },
+ {
+ path: '/effect',
+ name: 'Effect',
+ component: () => import('@/layouts/Menu.vue'),
+ props: { id: 'effect' },
+ redirect: { name: 'EffectLoginIndex' },
+ children: [
+ {
+ path: '/effect/login/index',
+ name: 'EffectLoginIndex',
+ component: () => import('@/views/effect/login/Index.vue')
+ }
+ ]
+ },
+ {
+ path: '/cross',
+ name: 'Cross',
+ component: () => import('@/layouts/Menu.vue'),
+ props: { id: 'cross' },
+ redirect: { name: 'CrossGoIndex' },
+ children: [
+ {
+ path: '/cross/go/index',
+ name: 'CrossGoIndex',
+ component: () => import('@/views/cross/go/Index.vue')
+ },
+ {
+ path: '/cross/java/index',
+ name: 'CrossJavaIndex',
+ component: () => import('@/views/cross/java/Index.vue')
+ },
+ {
+ path: '/cross/python/index',
+ name: 'CrossPythonIndex',
+ component: () => import('@/views/cross/python/Index.vue')
+ },
+ ]
+ },
+ ]
+ },
+ {
+ path: '/special',
+ children: [
+ {
+ path: 'subwindow',
+ name: 'SpecialSubwindowIpc',
+ component: () => import('@/views/os/subwindow/Ipc.vue')
+ },
+ {
+ path: '/login',
+ name: 'SpecialLoginWindow',
+ component: () => import('@/views/effect/login/Window.vue')
+ },
+ ]
+ },
+]
+
+export default constantRouterMap
\ No newline at end of file
diff --git a/frontend/src/router/subMenu.js b/frontend/src/router/subMenu.js
new file mode 100644
index 0000000..9c5b462
--- /dev/null
+++ b/frontend/src/router/subMenu.js
@@ -0,0 +1,101 @@
+// 子菜单
+export default {
+ framework: {
+ 'menu_100' : {
+ icon: 'profile',
+ title: '通信',
+ pageName: 'FrameworkSocketIpc',
+ params: {}
+ },
+ 'menu_101' : {
+ icon: 'profile',
+ title: 'http服务',
+ pageName: 'FrameworkSocketHttpServer',
+ params: {}
+ },
+ 'menu_102' : {
+ icon: 'profile',
+ title: 'socket服务',
+ pageName: 'FrameworkSocketSocketServer',
+ params: {}
+ },
+ 'menu_104' : {
+ icon: 'profile',
+ title: 'sqlite数据库',
+ pageName: 'FrameworkSqliteDBIndex',
+ params: {}
+ },
+ 'menu_105' : {
+ icon: 'profile',
+ title: '任务',
+ pageName: 'FrameworkJobsIndex',
+ params: {}
+ },
+ 'menu_106' : {
+ icon: 'profile',
+ title: '软件调用',
+ pageName: 'FrameworkSoftwareIndex',
+ params: {}
+ },
+ 'menu_107' : {
+ icon: 'profile',
+ title: '自动更新',
+ pageName: 'FrameworkUpdaterIndex',
+ params: {}
+ },
+ },
+ os: {
+ 'menu_100' : {
+ icon: 'profile',
+ title: '文件',
+ pageName: 'OsFileIndex',
+ params: {}
+ },
+ 'menu_102' : {
+ icon: 'profile',
+ title: '窗口',
+ pageName: 'OsWindowIndex',
+ params: {}
+ },
+ 'menu_103' : {
+ icon: 'profile',
+ title: '桌面通知',
+ pageName: 'OsNotificationIndex',
+ params: {}
+ },
+ 'menu_110' : {
+ icon: 'profile',
+ title: '图片',
+ pageName: 'OsFilePic',
+ params: {}
+ },
+ },
+ effect: {
+ 'menu_100' : {
+ icon: 'profile',
+ title: '登录',
+ pageName: 'EffectLoginIndex',
+ params: {}
+ }
+ },
+ cross: {
+ 'menu_100' : {
+ icon: 'profile',
+ title: 'go服务',
+ pageName: 'CrossGoIndex',
+ params: {}
+ },
+ 'menu_110' : {
+ icon: 'profile',
+ title: 'java服务',
+ pageName: 'CrossJavaIndex',
+ params: {}
+ },
+ 'menu_120' : {
+ icon: 'profile',
+ title: 'python服务',
+ pageName: 'CrossPythonIndex',
+ params: {}
+ },
+ },
+}
diff --git a/frontend/src/utils/iconList.js b/frontend/src/utils/iconList.js
new file mode 100644
index 0000000..e8597aa
--- /dev/null
+++ b/frontend/src/utils/iconList.js
@@ -0,0 +1,27 @@
+export default [
+ { name: '对话框', type: 'icon-duihuakuang' },
+ { name: '闹钟', type: 'icon-naozhong' },
+ { name: '笑脸', type: 'icon-xiaolian' },
+ { name: 'ok', type: 'icon-ok' },
+ { name: '风车', type: 'icon-fengche' },
+ { name: '汗颜', type: 'icon-hanyan' },
+ { name: '相机', type: 'icon-xiangji' },
+ { name: '礼物', type: 'icon-liwu' },
+ { name: '礼花', type: 'icon-lihua' },
+ { name: '扭蛋', type: 'icon-niudan' },
+ { name: '流星', type: 'icon-liuxing' },
+ { name: '风筝', type: 'icon-fengzheng' },
+ { name: '蛋糕', type: 'icon-dangao' },
+ { name: '泡泡', type: 'icon-paopao' },
+ { name: '购物', type: 'icon-gouwu' },
+ { name: '饮料', type: 'icon-yinliao' },
+ { name: '云彩', type: 'icon-yuncai' },
+ { name: '彩铅', type: 'icon-caiqian' },
+ { name: '纸飞机', type: 'icon-zhifeiji' },
+ { name: '点赞', type: 'icon-dianzan' },
+ { name: '煎蛋', type: 'icon-jiandan' },
+ { name: '小熊', type: 'icon-xiaoxiong' },
+ { name: '花', type: 'icon-hua' },
+ { name: '眼睛', type: 'icon-yanjing' },
+]
+
\ No newline at end of file
diff --git a/frontend/src/utils/ipcRenderer.js b/frontend/src/utils/ipcRenderer.js
new file mode 100644
index 0000000..b229b36
--- /dev/null
+++ b/frontend/src/utils/ipcRenderer.js
@@ -0,0 +1,33 @@
+const Renderer = (window.require && window.require('electron')) || window.electron || {};
+
+/**
+ * ipc
+ * 官方api说明:https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
+ *
+ * 属性/方法
+ * ipc.invoke(channel, param) - 发送异步消息(invoke/handle 模型)
+ * ipc.sendSync(channel, param) - 发送同步消息(send/on 模型)
+ * ipc.on(channel, listener) - 监听 channel, 当新消息到达,调用 listener
+ * ipc.once(channel, listener) - 添加一次性 listener 函数
+ * ipc.removeListener(channel, listener) - 为特定的 channel 从监听队列中删除特定的 listener 监听者
+ * ipc.removeAllListeners(channel) - 移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器
+ * ipc.send(channel, ...args) - 通过channel向主进程发送异步消息
+ * ipc.postMessage(channel, message, [transfer]) - 发送消息到主进程
+ * ipc.sendTo(webContentsId, channel, ...args) - 通过 channel 发送消息到带有 webContentsId 的窗口
+ * ipc.sendToHost(channel, ...args) - 消息会被发送到 host 页面上的 元素
+ */
+
+/**
+ * ipc
+ */
+const ipc = Renderer.ipcRenderer || undefined;
+
+/**
+ * 是否为EE环境
+ */
+const isEE = ipc ? true : false;
+
+export {
+ Renderer, ipc, isEE
+};
+
diff --git a/frontend/src/views/cross/go/Index.vue b/frontend/src/views/cross/go/Index.vue
new file mode 100644
index 0000000..57c0aa3
--- /dev/null
+++ b/frontend/src/views/cross/go/Index.vue
@@ -0,0 +1,104 @@
+
+
+
+
+ 1. 基础控制
+
+
+
+
+ 启动
+ 获取地址
+ kill
+ test
+
+
+
+
+ 2. 发送http请求
+
+
+
+
+
+
+
diff --git a/frontend/src/views/cross/java/Index.vue b/frontend/src/views/cross/java/Index.vue
new file mode 100644
index 0000000..30cca35
--- /dev/null
+++ b/frontend/src/views/cross/java/Index.vue
@@ -0,0 +1,102 @@
+
+
+
+
+ 1. 基础控制
+
+
+
+
+ 启动
+ 获取地址
+ kill
+ 查看
+
+
+
+
+ 2. 发送http请求
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/cross/python/Index.vue b/frontend/src/views/cross/python/Index.vue
new file mode 100644
index 0000000..1983c55
--- /dev/null
+++ b/frontend/src/views/cross/python/Index.vue
@@ -0,0 +1,101 @@
+
+
+
+
+ 1. 基础控制
+
+
+
+
+ 启动
+ 获取地址
+ kill
+ test
+
+
+
+
+ 2. 发送http请求
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/effect/login/Index.vue b/frontend/src/views/effect/login/Index.vue
new file mode 100644
index 0000000..d956bfc
--- /dev/null
+++ b/frontend/src/views/effect/login/Index.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/effect/login/Window.vue b/frontend/src/views/effect/login/Window.vue
new file mode 100644
index 0000000..f63f0d9
--- /dev/null
+++ b/frontend/src/views/effect/login/Window.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/framework/jobs/Index.vue b/frontend/src/views/framework/jobs/Index.vue
new file mode 100644
index 0000000..3fbcb62
--- /dev/null
+++ b/frontend/src/views/framework/jobs/Index.vue
@@ -0,0 +1,187 @@
+
+
+
+
+ 1. 任务 / 并发任务
+
+
+
+
+ 执行任务1
+ 进度: {{ progress1 }} , 进程pid: {{ progress1_pid }}
+ 暂停
+ 恢复
+ 关闭
+
+
+
+ 执行任务2
+ 进度: {{ progress2 }} , 进程pid: {{ progress2_pid }}
+ 暂停
+ 恢复
+ 关闭
+
+
+
+
+ 2. 任务池 / 并发任务
+
+
+
+
+ 创建进程池
+ 进程pids: {{ processPids }}
+
+
+
+ 执行任务3
+ 进度: {{ progress3 }} , 进程pid: {{ progress3_pid }}
+
+
+
+ 执行任务4
+ 进度: {{ progress4 }} , 进程pid: {{ progress4_pid }}
+
+
+
+ 执行任务5
+ 进度: {{ progress5 }} , 进程pid: {{ progress5_pid }}
+
+
+
+ 执行任务6
+ 进度: {{ progress6 }} , 进程pid: {{ progress6_pid }}
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/socket/HttpServer.vue b/frontend/src/views/framework/socket/HttpServer.vue
new file mode 100644
index 0000000..e182eb3
--- /dev/null
+++ b/frontend/src/views/framework/socket/HttpServer.vue
@@ -0,0 +1,114 @@
+
+
+
+
+ 1. 使用http与主进程通信
+
+
+
+
* 状态:{{ currentStatus }}
+
* 地址:{{ servicAddress }}
+
* 发送请求:
+ 打开【我的图片】
+
+
+
+
+ 2. 使用http与服务端通信
+
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/socket/Ipc.vue b/frontend/src/views/framework/socket/Ipc.vue
new file mode 100644
index 0000000..ec192f1
--- /dev/null
+++ b/frontend/src/views/framework/socket/Ipc.vue
@@ -0,0 +1,157 @@
+
+
+
+
+ 1. 发送异步消息
+
+
+
+
+ 发送 - 回调
+ 结果:{{ message1 }}
+
+
+
+ 发送 - async/await
+ 结果:{{ message2 }}
+
+
+
+
+
+ 2. 同步消息(不推荐,阻塞执行)
+
+
+
+
+ 同步消息
+ 结果:{{ message3 }}
+
+
+
+
+ 3. 长消息: 服务端持续向前端页面发消息
+
+
+
+
+ 开始
+ 结束
+ 结果:{{ messageString }}
+
+
+
+
+ 4. 多窗口通信:子窗口与主进程通信,子窗口互相通信
+
+
+
+
+ 打开新窗口2
+ 向新窗口2发消息
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/socket/SocketServer.vue b/frontend/src/views/framework/socket/SocketServer.vue
new file mode 100644
index 0000000..beb0321
--- /dev/null
+++ b/frontend/src/views/framework/socket/SocketServer.vue
@@ -0,0 +1,76 @@
+
+
+
+
+ 1. 使用socket与主进程通信
+
+
+
+
+ * 状态:{{ currentStatus }}
+
+
* 地址:{{ servicAddress }}
+
+
+
+ 2. 发送请求
+
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/software/Index.vue b/frontend/src/views/framework/software/Index.vue
new file mode 100644
index 0000000..063a536
--- /dev/null
+++ b/frontend/src/views/framework/software/Index.vue
@@ -0,0 +1,52 @@
+
+
+
+
+ 1. 调用其它软件 (exe、bash等可执行程序)
+
+
+
+ 注: 请先将【powershell.exe】复制到【electron-egg/build/extraResources】目录中
+
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/sqlitedb/Index.vue b/frontend/src/views/framework/sqlitedb/Index.vue
new file mode 100644
index 0000000..6f033a7
--- /dev/null
+++ b/frontend/src/views/framework/sqlitedb/Index.vue
@@ -0,0 +1,278 @@
+
+
+
+
+ 1. sqlite本地数据库
+
+
+
+
+
+ • 大数据量: 0-1024GB(单库)
+
+
+ • 高性能
+
+
+ • 类mysql语法
+
+
+
+
+
+ 2. 数据目录
+
+
+
+
+
+
+
+
+
+
+
+ 修改目录
+
+
+
+
+ 打开目录
+
+
+
+
+
+
+ 3. 测试数据
+
+
+
+
+
+ {{ all_list }}
+
+
+
+
+
+ 4. 添加数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加
+
+
+
+
+
+
+ 4. 获取数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查找
+
+
+
+
+
+ {{ userList }}
+
+
+
+
+
+ 5. 修改数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 更新
+
+
+
+
+
+
+ 6. 删除数据
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/framework/updater/Index.vue b/frontend/src/views/framework/updater/Index.vue
new file mode 100644
index 0000000..3917cf2
--- /dev/null
+++ b/frontend/src/views/framework/updater/Index.vue
@@ -0,0 +1,85 @@
+
+
+
+
+ 1. 自动更新
+
+
+
+
+
+ 2. 下载进度
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/os/file/Index.vue b/frontend/src/views/os/file/Index.vue
new file mode 100644
index 0000000..06b6386
--- /dev/null
+++ b/frontend/src/views/os/file/Index.vue
@@ -0,0 +1,120 @@
+
+
+
+
+ 1. 系统原生对话框
+
+
+
+
+ 消息提示(ipc)
+ 消息提示与确认(ipc)
+
+
+
+
+ 2. 选择保存目录
+
+
+
+
+
+
+
+
+
+ 修改目录
+
+
+
+
+
+
+ 3. 打开文件夹
+
+
+
+
+
+
+
+
+ 打开
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/os/file/Pic.vue b/frontend/src/views/os/file/Pic.vue
new file mode 100644
index 0000000..efd58b3
--- /dev/null
+++ b/frontend/src/views/os/file/Pic.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/os/notification/Index.vue b/frontend/src/views/os/notification/Index.vue
new file mode 100644
index 0000000..160130b
--- /dev/null
+++ b/frontend/src/views/os/notification/Index.vue
@@ -0,0 +1,86 @@
+
+
+
+
+ 1. 弹出桌面通知
+
+
+
+
+ 默认
+ 发出提示音
+ 点击通知触发事件
+ 关闭通知触发事件
+
+
+
+
+
+
diff --git a/frontend/src/views/os/subwindow/Ipc.vue b/frontend/src/views/os/subwindow/Ipc.vue
new file mode 100644
index 0000000..db40c3c
--- /dev/null
+++ b/frontend/src/views/os/subwindow/Ipc.vue
@@ -0,0 +1,143 @@
+
+
+
+
+ 1. 发送异步消息
+
+
+
+
+ 发送 - 回调
+ 结果:{{ message1 }}
+
+
+
+ 发送 - async/await
+ 结果:{{ message2 }}
+
+
+
+
+
+ 2. 同步消息(不推荐,阻塞执行)
+
+
+
+
+ 同步消息
+ 结果:{{ message3 }}
+
+
+
+
+ 3. 长消息: 服务端持续向前端页面发消息
+
+
+
+
+ 开始
+ 结束
+ 结果:{{ messageString }}
+
+
+
+
+ 4. 多窗口通信:窗口之间互相通信
+
+
+
+
+
+
+
diff --git a/frontend/src/views/os/window/Index.vue b/frontend/src/views/os/window/Index.vue
new file mode 100644
index 0000000..9f8192e
--- /dev/null
+++ b/frontend/src/views/os/window/Index.vue
@@ -0,0 +1,77 @@
+
+
+
+
+ 1. 新窗口中加载web内容
+
+
+
+
+
+ 2. 新窗口中加载html内容
+
+
+
+
+
+ 3. 新窗口中加载当前项目页面
+
+
+
+
+
+
+
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
new file mode 100644
index 0000000..e7b7e05
--- /dev/null
+++ b/frontend/vite.config.js
@@ -0,0 +1,57 @@
+import vue from '@vitejs/plugin-vue'
+import { defineConfig } from 'vite'
+import viteCompression from 'vite-plugin-compression'
+
+import path from 'path'
+// https://vitejs.dev/config/
+export default defineConfig(({ command, mode }) => {
+ return {
+ // 项目插件
+ plugins: [
+ vue(),
+ viteCompression({
+ verbose: true,
+ disable: false,
+ threshold: 1025,
+ algorithm: 'gzip',
+ ext: '.gz',
+ }),
+ ],
+ // 基础配置
+ base: './',
+ publicDir: 'public',
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src'),
+ },
+ },
+ css: {
+ preprocessorOptions: {
+ less: {
+ modifyVars: {
+ '@border-color-base': '#dce3e8',
+ },
+ javascriptEnabled: true,
+ },
+ },
+ },
+ build: {
+ outDir: 'dist',
+ assetsDir: 'assets',
+ assetsInlineLimit: 4096,
+ cssCodeSplit: true,
+ brotliSize: false,
+ sourcemap: false,
+ minify: 'terser',
+ terserOptions: {
+ compress: {
+ // 生产环境去除console及debug
+ drop_console: false,
+ drop_debugger: true,
+ },
+ },
+ },
+ }
+})
+
+
diff --git a/go/api/example.go b/go/api/example.go
new file mode 100644
index 0000000..0e4f63e
--- /dev/null
+++ b/go/api/example.go
@@ -0,0 +1,66 @@
+package api
+
+import (
+ "net/http"
+
+ "github.com/wallace5303/ee-go/eapp"
+ "github.com/wallace5303/ee-go/ehelper"
+ "github.com/wallace5303/ee-go/ehttp/router"
+ "github.com/wallace5303/ee-go/elog"
+
+ "github.com/gin-gonic/gin"
+ //"electron-egg/demo/sql/sqlitelib"
+)
+
+// 使用 router Ctx
+func Hello(c *router.Ctx) {
+ ret := ehelper.GetJson()
+ defer c.JSON(ret)
+
+ ret.Data = "hello electron-egg"
+ elog.Logger.Info(" print Hello ")
+}
+
+// 使用 gin Context
+func Info(gc *gin.Context) {
+ ret := ehelper.GetJson()
+ defer gc.JSON(http.StatusOK, ret)
+
+ elog.Logger.Info(" print info ")
+}
+
+func Exit(c *router.Ctx) {
+ ret := ehelper.GetJson()
+ defer c.JSON(ret)
+
+ eapp.Close()
+}
+
+func SetValue(c *router.Ctx) {
+ ret := ehelper.GetJson()
+ defer c.JSON(ret)
+
+ // arg, ok := c.ArgJson()
+ // if !ok {
+ // return
+ // }
+
+ // keyName := arg["key"].(string)
+ // vallue := arg["value"]
+
+ // sqlitelib.SetStatData(keyName, vallue)
+}
+
+func GetValue(c *router.Ctx) {
+ ret := ehelper.GetJson()
+ defer c.JSON(ret)
+
+ // arg, ok := c.ArgJson()
+ // if !ok {
+ // return
+ // }
+
+ // keyName := arg["key"].(string)
+
+ // ret.Data = sqlitelib.GetStat(keyName)
+}
diff --git a/go/config/.air.toml b/go/config/.air.toml
new file mode 100644
index 0000000..f0e1444
--- /dev/null
+++ b/go/config/.air.toml
@@ -0,0 +1,61 @@
+root = "."
+tmp_dir = "tmp"
+
+[build]
+# 编译使用的shell命令
+cmd = "go build -o ./tmp/goapp ./main.go"
+
+# 由`cmd`命令得到的二进制文件名
+bin = "./tmp/goapp --basedir=../ --env=dev --port=7073"
+# 在运行二进制文件时添加额外的参数 (bin/full_bin)。将运行“./tmp/main hello world”
+# args_bin = ["hello", "world"]
+
+# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间/毫秒
+delay = 1000
+
+# 忽略(不监听)文件的扩展名或目录
+exclude_dir = ["tmp", "public"]
+
+# 监听指定目录的文件
+# include_dir = []
+
+# 监听扩展名的文件
+include_ext = ["go", "mod", "sum", "json", "tpl", "tmpl", "html"]
+
+# 忽略(不监听)指定文件
+exclude_file = []
+
+# 忽略符合通过正则匹配到的文件
+exclude_regex = []
+
+# 忽略未进行修改的文件
+exclude_unchanged = true
+
+# 按照目录的符号链接
+follow_symlink = false
+
+# 杀死进程前发送中断信号(Windows不支持)
+send_interrupt = true
+# 发送中断信号后延迟时间/毫秒
+kill_delay = 2000
+
+# 发生构建错误时,停止运行旧的二进制文件
+stop_on_error = true
+
+# 这个日志文件放在你的`tmp_dir`中
+log = "air.log"
+
+[log]
+# 显示日志时间
+time = true
+
+[color]
+# 自定义每个部分的颜色。如果未找到颜色,请使用原始应用程序日志。
+main = "magenta"
+watcher = "cyan"
+build = "yellow"
+runner = "green"
+
+[misc]
+# 退出时删除 tmp 目录
+clean_on_exit = true
\ No newline at end of file
diff --git a/go/config/.air.windows.toml b/go/config/.air.windows.toml
new file mode 100644
index 0000000..a8b85d2
--- /dev/null
+++ b/go/config/.air.windows.toml
@@ -0,0 +1,61 @@
+root = "."
+tmp_dir = "tmp"
+
+[build]
+# 编译使用的shell命令
+cmd = "go build -o ./tmp/goapp.exe ./main.go"
+
+# 由`cmd`命令得到的二进制文件名
+bin = "./tmp/goapp.exe --basedir=../ --env=dev --port=7073"
+# 在运行二进制文件时添加额外的参数 (bin/full_bin)。将运行“./tmp/main hello world”
+# args_bin = ["hello", "world"]
+
+# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间/毫秒
+delay = 1000
+
+# 忽略(不监听)文件的扩展名或目录
+exclude_dir = ["tmp", "public"]
+
+# 监听指定目录的文件
+# include_dir = []
+
+# 监听扩展名的文件
+include_ext = ["go", "mod", "sum", "json", "tpl", "tmpl", "html"]
+
+# 忽略(不监听)指定文件
+exclude_file = []
+
+# 忽略符合通过正则匹配到的文件
+exclude_regex = []
+
+# 忽略未进行修改的文件
+exclude_unchanged = true
+
+# 按照目录的符号链接
+follow_symlink = false
+
+# 杀死进程前发送中断信号(Windows不支持)
+send_interrupt = true
+# 发送中断信号后延迟时间/毫秒
+kill_delay = 2000
+
+# 发生构建错误时,停止运行旧的二进制文件
+stop_on_error = true
+
+# 这个日志文件放在你的`tmp_dir`中
+log = "air.log"
+
+[log]
+# 显示日志时间
+time = true
+
+[color]
+# 自定义每个部分的颜色。如果未找到颜色,请使用原始应用程序日志。
+main = "magenta"
+watcher = "cyan"
+build = "yellow"
+runner = "green"
+
+[misc]
+# 退出时删除 tmp 目录
+clean_on_exit = true
\ No newline at end of file
diff --git a/go/config/config.default.json b/go/config/config.default.json
new file mode 100644
index 0000000..6132dca
--- /dev/null
+++ b/go/config/config.default.json
@@ -0,0 +1,27 @@
+{
+ "logger": {
+ "output_json": false,
+ "level": "info",
+ "filename": "ee-go.log",
+ "max_size": 1024,
+ "max_age": 10
+ },
+ "core_logger": {
+ "output_json": false,
+ "level": "info",
+ "filename": "ee-go-core.log",
+ "max_size": 1024,
+ "max_age": 10
+ },
+ "http": {
+ "enable": true,
+ "port": 7073,
+ "network": false
+ },
+ "static": {
+ "enable": true,
+ "package": "public/package.json",
+ "config": "public/config",
+ "dist": "public/dist"
+ }
+}
\ No newline at end of file
diff --git a/go/config/config.local.json b/go/config/config.local.json
new file mode 100644
index 0000000..2268fe0
--- /dev/null
+++ b/go/config/config.local.json
@@ -0,0 +1,9 @@
+{
+ "logger": {
+ "output_json": false,
+ "level": "info",
+ "filename": "ee-go.log",
+ "max_size": 1024,
+ "max_age": 20
+ }
+}
\ No newline at end of file
diff --git a/go/config/config.prod.json b/go/config/config.prod.json
new file mode 100644
index 0000000..d2b0aec
--- /dev/null
+++ b/go/config/config.prod.json
@@ -0,0 +1,9 @@
+{
+ "logger": {
+ "output_json": false,
+ "level": "info",
+ "filename": "ee-go.log",
+ "max_size": 1024,
+ "max_age": 30
+ }
+}
\ No newline at end of file
diff --git a/go/demo/index.go b/go/demo/index.go
new file mode 100644
index 0000000..8c92980
--- /dev/null
+++ b/go/demo/index.go
@@ -0,0 +1,26 @@
+package demo
+
+import (
+ "electron-egg/demo/job"
+ //"electron-egg/demo/sql/sqlitelib"
+ "electron-egg/demo/util"
+
+ "github.com/wallace5303/ee-go/eapp"
+ "github.com/wallace5303/ee-go/elog"
+)
+
+// 使用 router Ctx
+func Index() {
+ elog.Logger.Info("Start Demo")
+
+ // 初始化基础数据
+ util.Boot()
+ // 初始化数据库
+ //sqlitelib.InitDB(false)
+ // 初始化任务
+ job.Boot()
+ // 注册关闭前的处理函数
+ eapp.Register("beforeClose", func() {
+ //sqlitelib.CloseDatabase()
+ })
+}
diff --git a/go/demo/job/index.go b/go/demo/job/index.go
new file mode 100644
index 0000000..5339007
--- /dev/null
+++ b/go/demo/job/index.go
@@ -0,0 +1,37 @@
+package job
+
+import (
+ "time"
+
+ "github.com/wallace5303/ee-go/elog"
+ "github.com/wallace5303/ee-go/eruntime"
+ "github.com/wallace5303/ee-go/etask"
+ "github.com/wallace5303/ee-go/eutil"
+)
+
+var (
+ checkStatusInterval = 5
+)
+
+func Boot() {
+ if eruntime.IsDev() {
+ checkStatusInterval = 2
+ }
+ go etask.Every(1000*time.Millisecond, etask.ExecTask)
+ go etask.Every(time.Duration(checkStatusInterval)*time.Second, etask.Status)
+
+ // test task
+ AddTestTask()
+}
+
+func AddTestTask() {
+ count := 10
+ for i := 0; i < count; i++ {
+ etask.AddTask("task.demo", hello)
+ }
+}
+
+func hello() {
+ defer eutil.Recover()
+ elog.Logger.Info("[task] hello")
+}
diff --git a/go/demo/sql/sqlitelib/sql.go b/go/demo/sql/sqlitelib/sql.go
new file mode 100644
index 0000000..6adb921
--- /dev/null
+++ b/go/demo/sql/sqlitelib/sql.go
@@ -0,0 +1,208 @@
+package sqlitelib
+
+import (
+ "database/sql"
+ "errors"
+ "os"
+ "runtime"
+ "runtime/debug"
+ "strings"
+ "sync"
+ "time"
+
+ "electron-egg/demo/util"
+
+ "github.com/wallace5303/ee-go/ehelper"
+ "github.com/wallace5303/ee-go/elog"
+
+ _ "github.com/glebarez/go-sqlite"
+)
+
+var (
+ db *sql.DB
+)
+
+var initDBLock = sync.Mutex{}
+
+func InitDB(forceRebuild bool) (err error) {
+ initDBLock.Lock()
+ defer initDBLock.Unlock()
+
+ elog.Logger.Infof("Init database")
+ initDBConnection()
+
+ if !forceRebuild {
+ // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建
+ dbVer := GetDatabaseVer()
+ if util.DatabaseVer == dbVer {
+ return
+ }
+
+ elog.Logger.Infof("rebuilding database ......")
+ }
+
+ // 不存在库或者版本不一致都会走到这里
+ CloseDatabase()
+ if ehelper.FileIsExist(util.DBPath) {
+ if err = removeDatabaseFile(); err != nil {
+ elog.Logger.Errorf("remove database file [%s] failed: %s", util.DBPath, err)
+
+ err = nil
+ }
+ }
+
+ initDBConnection()
+ initDBTables()
+
+ return
+}
+
+func initDBConnection() {
+ if db != nil {
+ CloseDatabase()
+ }
+
+ dsn := util.DBPath + "?_pragma=busy_timeout(7000)" +
+ "&_pragma=journal_mode(WAL)" +
+ "&_pragma=synchronous(1)" +
+ "&_pragma=mmap_size(2684354560)" +
+ "&_pragma=cache_size(-20480)" +
+ "&_pragma=page_size(32768)" +
+ "&_pragma=case_sensitive_like(OFF)"
+
+ var err error
+ db, err = sql.Open("sqlite", dsn)
+ if err != nil {
+ elog.Logger.Errorf("create database failed: %s", err)
+ }
+ elog.Logger.Infof("DB data source name: %s", dsn)
+
+ db.SetMaxIdleConns(20)
+ db.SetMaxOpenConns(20)
+ db.SetConnMaxLifetime(365 * 24 * time.Hour)
+}
+
+func initDBTables() {
+ elog.Logger.Infof("init tables ......")
+
+ _, err := db.Exec("DROP TABLE IF EXISTS stat")
+ if err != nil {
+ elog.Logger.Errorf("drop table [stat] failed: %s", err)
+ }
+ _, err = db.Exec("CREATE TABLE stat ( key varchar ( 255 ) NOT NULL DEFAULT '', value text DEFAULT '', PRIMARY KEY ( key ) );")
+ if err != nil {
+ elog.Logger.Errorf("create table [stat] failed: %s", err)
+ }
+
+ _, err = db.Exec("DROP TABLE IF EXISTS storage")
+ if err != nil {
+ elog.Logger.Errorf("drop table [storage] failed: %s", err)
+ }
+ _, err = db.Exec("CREATE TABLE storage ( name varchar ( 255 ), value text, expires_time datetime );")
+ if err != nil {
+ elog.Logger.Errorf("create table [storage] failed: %s", err)
+ }
+ _, err = db.Exec("CREATE UNIQUE INDEX index_name ON storage ( name ASC );")
+ if err != nil {
+ elog.Logger.Errorf("create INDEX [index_name] failed: %s", err)
+ }
+
+ setDatabaseVer()
+}
+
+func CloseDatabase() (err error) {
+ if db == nil {
+ return
+ }
+
+ err = db.Close()
+ debug.FreeOSMemory()
+ runtime.GC()
+ return
+}
+
+func removeDatabaseFile() (err error) {
+ err = os.RemoveAll(util.DBPath)
+ if err != nil {
+ return
+ }
+ err = os.RemoveAll(util.DBPath + "-shm")
+ if err != nil {
+ return
+ }
+ err = os.RemoveAll(util.DBPath + "-wal")
+ if err != nil {
+ return
+ }
+ return
+}
+
+func beginTx() (tx *sql.Tx, err error) {
+ if tx, err = db.Begin(); err != nil {
+ elog.Logger.Errorf("begin tx failed: %s\n", err)
+ if strings.Contains(err.Error(), "database is locked") {
+ os.Exit(1)
+ }
+ }
+ return
+}
+
+func commitTx(tx *sql.Tx) (err error) {
+ if tx == nil {
+ elog.Logger.Errorf("tx is nil")
+ return errors.New("tx is nil")
+ }
+
+ if err = tx.Commit(); err != nil {
+ elog.Logger.Errorf("commit tx failed: %s\n", err)
+ }
+ return
+}
+
+func execStmtTx(tx *sql.Tx, stmt string, args ...interface{}) (err error) {
+ elog.Logger.Infof("[sql/sqlitelib/execStmtTx] sql: %s args: %+v", stmt, args)
+
+ if _, err = tx.Exec(stmt, args...); err != nil {
+ if strings.Contains(err.Error(), "database disk image is malformed") {
+ tx.Rollback()
+ CloseDatabase()
+ elog.Logger.Errorf("database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it", util.DBPath)
+ }
+ elog.Logger.Errorf("exec database stmt [%s] failed: %s\n %s", stmt)
+ return
+ }
+ return
+}
+
+func prepareExecInsertTx(tx *sql.Tx, stmtSQL string, args []interface{}) (err error) {
+ stmt, err := tx.Prepare(stmtSQL)
+
+ if err != nil {
+ return
+ }
+
+ if _, err = stmt.Exec(args...); err != nil {
+ elog.Logger.Errorf("exec database stmt [%s] failed: %s", stmtSQL, err)
+ return
+ }
+ return
+}
+
+func queryRow(query string, args ...interface{}) *sql.Row {
+ elog.Logger.Infof("[sql/sqlitelib/queryRow] sql: %s args: %+v", query, args)
+ query = strings.TrimSpace(query)
+ if query == "" {
+ elog.Logger.Errorf("statement is empty")
+ return nil
+ }
+ return db.QueryRow(query, args...)
+}
+
+func query(query string, args ...interface{}) (*sql.Rows, error) {
+ elog.Logger.Infof("[sql/sqlitelib/query] sql: %s args: %+v", query, args)
+ query = strings.TrimSpace(query)
+ if query == "" {
+ return nil, errors.New("statement is empty")
+ }
+ return db.Query(query, args...)
+}
diff --git a/go/demo/sql/sqlitelib/stat.go b/go/demo/sql/sqlitelib/stat.go
new file mode 100644
index 0000000..dc5c608
--- /dev/null
+++ b/go/demo/sql/sqlitelib/stat.go
@@ -0,0 +1,84 @@
+package sqlitelib
+
+import (
+ "database/sql"
+ "fmt"
+ "strings"
+
+ "electron-egg/demo/util"
+
+ "github.com/wallace5303/ee-go/elog"
+)
+
+type Stat struct {
+ Key string `json:"key"`
+ Val string `json:"value"`
+}
+
+const (
+ dbVersionName = "duola_database_version"
+)
+
+func GetDatabaseVer() (ret string) {
+ stmt := "SELECT value FROM stat WHERE `key` = '" + dbVersionName + "'"
+ row := db.QueryRow(stmt)
+ if err := row.Scan(&ret); nil != err {
+ if !strings.Contains(err.Error(), "no such table") {
+ elog.Logger.Errorf("query database version failed: %s", err)
+ }
+ }
+
+ return
+}
+
+func setDatabaseVer() {
+ key := dbVersionName
+ tx, err := beginTx()
+ if err != nil {
+ return
+ }
+ if err = PutStat(tx, key, util.DatabaseVer); err != nil {
+ return
+ }
+ commitTx(tx)
+}
+
+func PutStat(tx *sql.Tx, key, value string) (err error) {
+ stmt := "DELETE FROM stat WHERE `key` = '" + key + "'"
+ if err = execStmtTx(tx, stmt); err != nil {
+ return
+ }
+
+ stmt = "INSERT INTO stat VALUES ('" + key + "', '" + value + "')"
+ err = execStmtTx(tx, stmt)
+ return
+}
+
+func GetStat(key string) (ret string) {
+ stmt := "SELECT value FROM stat WHERE `key` = '" + key + "'"
+ row := queryRow(stmt)
+ row.Scan(&ret)
+ return
+}
+
+func SetStatData(key string, value any) (err error) {
+ tx, bErr := beginTx()
+ if bErr != nil {
+ fmt.Printf("SetStatData bErr %v\n", bErr)
+ return
+ }
+
+ stmt := "DELETE FROM stat WHERE `key` = '" + key + "'"
+ if err = execStmtTx(tx, stmt); err != nil {
+ return
+ }
+
+ stmt = "INSERT INTO stat (key, value) VALUES (?, ?)"
+ err = execStmtTx(tx, stmt, key, value)
+ if err != nil {
+ return
+ }
+
+ commitTx(tx)
+ return
+}
diff --git a/go/demo/util/index.go b/go/demo/util/index.go
new file mode 100644
index 0000000..77b2fef
--- /dev/null
+++ b/go/demo/util/index.go
@@ -0,0 +1,58 @@
+package util
+
+import (
+ "os"
+ "path/filepath"
+
+ "github.com/wallace5303/ee-go/elog"
+ "github.com/wallace5303/ee-go/eruntime"
+)
+
+// 基础
+const (
+ Version = "0.1.0"
+ LocalHost = "127.0.0.1" // 伺服地址
+
+ // DatabaseVer 数据库版本
+ DatabaseVer = "20240101"
+)
+
+// 初始化
+var (
+ DBName = "ee.db"
+ DBPath string // SQLite 数据库文件路径
+
+ ConfDir string // 用户配置目录路径
+ LocalDir string // 用户数据目录路径
+ DBDir string // 用户DB目录路径
+ TmpDir string // 用户临时目录路径
+)
+
+func Boot() {
+ initPathDir()
+}
+
+func initPathDir() {
+ ConfDir = filepath.Join(eruntime.DataDir, "conf")
+ LocalDir = filepath.Join(eruntime.DataDir, "local")
+ DBDir = filepath.Join(eruntime.DataDir, "db")
+ DBPath = filepath.Join(DBDir, DBName)
+ TmpDir = eruntime.TmpDir
+ // 创建
+ createDir()
+}
+
+func createDir() {
+ if err := os.MkdirAll(ConfDir, 0755); nil != err && !os.IsExist(err) {
+ elog.Logger.Errorf("create conf folder [%s] failed: %s", ConfDir, err)
+ }
+ if err := os.MkdirAll(LocalDir, 0755); nil != err && !os.IsExist(err) {
+ elog.Logger.Errorf("create data folder [%s] failed: %s", LocalDir, err)
+ }
+ if err := os.MkdirAll(TmpDir, 0755); nil != err && !os.IsExist(err) {
+ elog.Logger.Errorf("create tmp folder [%s] failed: %s", TmpDir, err)
+ }
+ if err := os.MkdirAll(DBDir, 0755); nil != err && !os.IsExist(err) {
+ elog.Logger.Errorf("create db folder [%s] failed: %s", DBDir, err)
+ }
+}
diff --git a/go/go.mod b/go/go.mod
new file mode 100644
index 0000000..b305292
--- /dev/null
+++ b/go/go.mod
@@ -0,0 +1,75 @@
+module electron-egg
+
+go 1.20
+
+require (
+ github.com/gin-gonic/gin v1.9.1
+ github.com/glebarez/go-sqlite v1.22.0
+ github.com/wallace5303/ee-go v1.2.1
+)
+
+require (
+ github.com/bytedance/sonic v1.9.1 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+ github.com/gin-contrib/gzip v0.0.6 // indirect
+ github.com/gin-contrib/sessions v0.0.5 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/google/uuid v1.5.0 // indirect
+ github.com/gorilla/context v1.1.1 // indirect
+ github.com/gorilla/securecookie v1.1.1 // indirect
+ github.com/gorilla/sessions v1.2.1 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+ github.com/leodido/go-urn v1.2.4 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/magiconair/properties v1.8.7 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/mssola/useragent v1.0.0 // indirect
+ github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/sagikazarmark/locafero v0.3.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/shirou/gopsutil/v3 v3.23.8 // indirect
+ github.com/shoenig/go-m1cpu v0.1.6 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
+ github.com/spf13/afero v1.10.0 // indirect
+ github.com/spf13/cast v1.5.1 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/viper v1.17.0 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.12 // indirect
+ github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ github.com/yusufpapurcu/wmi v1.2.3 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ go.uber.org/zap v1.26.0 // indirect
+ golang.org/x/arch v0.3.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+ golang.org/x/net v0.16.0 // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ modernc.org/libc v1.37.6 // indirect
+ modernc.org/mathutil v1.6.0 // indirect
+ modernc.org/memory v1.7.2 // indirect
+ modernc.org/sqlite v1.28.0 // indirect
+)
diff --git a/go/main.go b/go/main.go
new file mode 100644
index 0000000..1afaa83
--- /dev/null
+++ b/go/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "embed"
+
+ "github.com/wallace5303/ee-go/eboot"
+
+ "electron-egg/demo"
+ "electron-egg/router"
+)
+
+var (
+ //go:embed public/**
+ staticFS embed.FS
+)
+
+func main() {
+ // Initialize ee-go
+ ego := eboot.New(staticFS)
+
+ // User business logic
+ router.Api()
+
+ // demo
+ demo.Index()
+
+ // ee-go runtime
+ ego.Run()
+}
diff --git a/go/router/router.go b/go/router/router.go
new file mode 100644
index 0000000..d57977a
--- /dev/null
+++ b/go/router/router.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+ "electron-egg/api"
+
+ eRouter "github.com/wallace5303/ee-go/ehttp/router"
+)
+
+func Api() {
+
+ // 注册路由
+ eRouter.Handle("GET", "/api/hello", api.Hello)
+ eRouter.Handle("GET", "/api/exit", api.Exit)
+ eRouter.Handle("GET", "/api/getValue", api.GetValue)
+ eRouter.Handle("POST", "/api/setValue", api.SetValue)
+
+ // 使用 gin 注册路由
+ eRouter.GinRouter.GET("/api/info", api.Info)
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..269c061
--- /dev/null
+++ b/package.json
@@ -0,0 +1,63 @@
+{
+ "name": "ee-next",
+ "version": "4.0.0",
+ "description": "A fast, desktop software development framework",
+ "main": "./public/electron/main.js",
+ "scripts": {
+ "dev": "ee-bin dev",
+ "build": "npm run build-frontend && npm run build-electron && ee-bin encrypt",
+ "start": "ee-bin start",
+ "dev-frontend": "ee-bin dev --serve=frontend",
+ "dev-electron": "ee-bin dev --serve=electron",
+ "dev-go": "ee-bin exec --cmds=go",
+ "dev-go-w": "ee-bin exec --cmds=go_w",
+ "dev-python": "ee-bin exec --cmds=python",
+ "build-frontend": "ee-bin build --cmds=frontend && ee-bin move --flag=frontend_dist",
+ "build-electron": "ee-bin build --cmds=electron",
+ "build-go-w": "ee-bin move --flag=go_static,go_config,go_package && ee-bin build --cmds=go_w",
+ "build-go-m": "ee-bin move --flag=go_static,go_config,go_package,go_images && ee-bin build --cmds=go_m",
+ "build-go-l": "ee-bin move --flag=go_static,go_config,go_package,go_images && ee-bin build --cmds=go_l",
+ "build-python": "ee-bin build --cmds=python && ee-bin move --flag=python_dist",
+ "encrypt": "ee-bin encrypt",
+ "icon": "ee-bin icon",
+ "re-sqlite": "electron-rebuild -f -w better-sqlite3",
+ "build-w": "ee-bin build --cmds=win64",
+ "build-we": "ee-bin build --cmds=win_e",
+ "build-m": "ee-bin build --cmds=mac",
+ "build-m-arm64": "ee-bin build --cmds=mac_arm64",
+ "build-l": "ee-bin build --cmds=linux",
+ "debug-dev": "cross-env DEBUG=ee-* ee-bin dev",
+ "debug-encrypt": "ee-bin encrypt",
+ "debug-electron": "cross-env DEBUG=ee-* ee-bin dev --serve=electron",
+ "debug-move": "ee-bin move --flag=frontend_dist"
+ },
+ "repository": "https://github.com/dromara/electron-egg.git",
+ "keywords": [
+ "Electron",
+ "electron-egg",
+ "ElectronEgg"
+ ],
+ "author": "哆啦好梦, Inc <530353222@qq.com>",
+ "license": "Apache",
+ "devDependencies": {
+ "@electron-toolkit/tsconfig": "^1.0.1",
+ "@electron/rebuild": "^3.7.1",
+ "@types/better-sqlite3": "^7.6.12",
+ "@types/node": "^22.10.2",
+ "chokidar": "^4.0.3",
+ "cross-env": "^7.0.3",
+ "debug": "^4.4.0",
+ "ee-bin": "file:.yalc/ee-bin",
+ "electron": "^31.7.6",
+ "electron-builder": "^25.1.8",
+ "esbuild": "0.24.2",
+ "typescript": "^5.4.2"
+ },
+ "dependencies": {
+ "axios": "^1.7.9",
+ "better-sqlite3": "^11.7.0",
+ "dayjs": "^1.11.13",
+ "ee-core": "file:.yalc/ee-core",
+ "electron-updater": "^6.3.8"
+ }
+}
diff --git a/public/html/loading.html b/public/html/loading.html
new file mode 100644
index 0000000..9d1520b
--- /dev/null
+++ b/public/html/loading.html
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/html/view_example.html b/public/html/view_example.html
new file mode 100644
index 0000000..d480ac7
--- /dev/null
+++ b/public/html/view_example.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+ 这是一个html页面
+
+
+
\ No newline at end of file
diff --git a/public/images/example/aw-3.png b/public/images/example/aw-3.png
new file mode 100644
index 0000000..ddebf47
Binary files /dev/null and b/public/images/example/aw-3.png differ
diff --git a/public/images/example/ee-mac-home.png b/public/images/example/ee-mac-home.png
new file mode 100644
index 0000000..7fd5093
Binary files /dev/null and b/public/images/example/ee-mac-home.png differ
diff --git a/public/images/example/ee-project-7.png b/public/images/example/ee-project-7.png
new file mode 100644
index 0000000..adc4a31
Binary files /dev/null and b/public/images/example/ee-project-7.png differ
diff --git a/public/images/example/ee-win-home.png b/public/images/example/ee-win-home.png
new file mode 100644
index 0000000..5a83862
Binary files /dev/null and b/public/images/example/ee-win-home.png differ
diff --git a/public/images/example/ee-zs.png b/public/images/example/ee-zs.png
new file mode 100644
index 0000000..ab27baa
Binary files /dev/null and b/public/images/example/ee-zs.png differ
diff --git a/public/images/example/ee_game_1.png b/public/images/example/ee_game_1.png
new file mode 100644
index 0000000..c08dcb7
Binary files /dev/null and b/public/images/example/ee_game_1.png differ
diff --git a/public/images/example/fm-p1.png b/public/images/example/fm-p1.png
new file mode 100644
index 0000000..45ae065
Binary files /dev/null and b/public/images/example/fm-p1.png differ
diff --git a/public/images/example/fm-p2.png b/public/images/example/fm-p2.png
new file mode 100644
index 0000000..37360c9
Binary files /dev/null and b/public/images/example/fm-p2.png differ
diff --git a/public/images/example/fm-p4.png b/public/images/example/fm-p4.png
new file mode 100644
index 0000000..8ed280a
Binary files /dev/null and b/public/images/example/fm-p4.png differ
diff --git a/public/images/example/im-p1.png b/public/images/example/im-p1.png
new file mode 100644
index 0000000..440a47c
Binary files /dev/null and b/public/images/example/im-p1.png differ
diff --git a/public/images/example/im-p5.png b/public/images/example/im-p5.png
new file mode 100644
index 0000000..0b7ac54
Binary files /dev/null and b/public/images/example/im-p5.png differ
diff --git a/public/images/example/im2-p1.png b/public/images/example/im2-p1.png
new file mode 100644
index 0000000..44d7145
Binary files /dev/null and b/public/images/example/im2-p1.png differ
diff --git a/public/images/example/logo.png b/public/images/example/logo.png
new file mode 100644
index 0000000..95dc60b
Binary files /dev/null and b/public/images/example/logo.png differ
diff --git a/public/images/example/lol-zhanji.png b/public/images/example/lol-zhanji.png
new file mode 100644
index 0000000..7166c2d
Binary files /dev/null and b/public/images/example/lol-zhanji.png differ
diff --git a/public/images/example/rq-1.png b/public/images/example/rq-1.png
new file mode 100644
index 0000000..aeca28e
Binary files /dev/null and b/public/images/example/rq-1.png differ
diff --git a/public/images/example/rq-2.png b/public/images/example/rq-2.png
new file mode 100644
index 0000000..ec793a1
Binary files /dev/null and b/public/images/example/rq-2.png differ
diff --git a/public/images/example/ubuntu-db.png b/public/images/example/ubuntu-db.png
new file mode 100644
index 0000000..fbcf44e
Binary files /dev/null and b/public/images/example/ubuntu-db.png differ
diff --git a/public/images/example/uos-home.png b/public/images/example/uos-home.png
new file mode 100644
index 0000000..b20c3dd
Binary files /dev/null and b/public/images/example/uos-home.png differ
diff --git a/public/images/example/v3-home.png b/public/images/example/v3-home.png
new file mode 100644
index 0000000..7535541
Binary files /dev/null and b/public/images/example/v3-home.png differ
diff --git a/public/images/example/vue-antd.png b/public/images/example/vue-antd.png
new file mode 100644
index 0000000..4747cfc
Binary files /dev/null and b/public/images/example/vue-antd.png differ
diff --git a/public/images/logo-32.png b/public/images/logo-32.png
new file mode 100644
index 0000000..60445f3
Binary files /dev/null and b/public/images/logo-32.png differ
diff --git a/public/images/logo.png b/public/images/logo.png
new file mode 100644
index 0000000..004feec
Binary files /dev/null and b/public/images/logo.png differ
diff --git a/public/images/tray.png b/public/images/tray.png
new file mode 100644
index 0000000..e88d02d
Binary files /dev/null and b/public/images/tray.png differ
diff --git a/public/ssl/localhost+1.key b/public/ssl/localhost+1.key
new file mode 100644
index 0000000..9a04a92
--- /dev/null
+++ b/public/ssl/localhost+1.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDpWYqPkljVtDNp
+JVwlcOxNVihQPf4T3Q/tuIt5znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbY
+m6AJihkKy0iKj1mVS9TPCzqcHFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6
+ocWp6nw1gMhrMIxqT2MQIQX16SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWn
+dS4GpbT39rW9cmbJrGHCvl8bsm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAu
+ls8p/lAoIvQy8MkLLM5b392CLMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP
+8/8a2bU/AgMBAAECggEBAIzFZ8GVF+JUA2+7CgvMQ8Gj6E4AF/cDtUhDvGCPHG8n
+PeCk4W4pY+jMFnI3PxmDvhOvIlZYqGeAKjUiLTmUBedtGyX7tJ9MT+VXcNQchlSo
+/Jd0mr/LWw/OPispOlLJBYjfGRV6KaIQtLnqPcRzoNrmBgIkF5FKswhX47CmIyu8
+eEXmDsqXzWmYwdOHLNDshLOrCgFgRnqK7HjxvNqb9k4qv3V9WxlJYITK7L4eJNcZ
+XYvGl+QV1+n153phyYnHcbohzvE44Hv8e4hiY9uYNdWckNBNFLpk9vEmUjewCRT1
+kL30woifUhZCalXBIfKEHNZg23teOMHNZTqcS+ER64ECgYEA7BAWVWS/8b9h1AmP
+SYhJgudb+ck1ItuvEDjonKuUCTBYm0iQI7cgAimasIBybRO8zwKstvWGDnUJJEkB
+Oh7AXF6CbKuP7navTBAaF3AXsNfDgQuEwtEg78iqiQzTyp6FOvvOT2aOtzi86Ju1
+zwKu+DLV3ETeokHWrQmV1uZnbZMCgYEA/Q7LdEEvZxz8OqOjtj8iKLWAv0WvnHTO
+Zjqn/BbXi2PyI8d5ntIUPEgm/MRpvXTa465Uu8Orujfq51Z/oFbyvhhEOWT/sVn/
+fzzbH4xXb3bLJ3D+LILQzsm4d8yrV66Re1ehFVxRPIy5RTpssED7bOns9ds4lsHT
+W9p7ibYRBSUCgYAMpJftnuXA1tUwfAqWj5wQTL/aUvJrmYR4w/OBYJcfHt3AA1Tk
+9MvcEcpdJaP7P5FfLO9/JQs2/wGsVdSg/kCjMdSeaVneFbExy7L6CmDacdPgt3M2
+0+iFryOjD3LQaUkNbasRCZcfLQTBGIXWPniMhnx5vZ6G5ivPPLIvvktPzQKBgQCd
+s/yi5ISwE+Y0fQpnZwzYpdQoXztDm5+NIfzSI0IMgirClWt7yJwHvUdeuuDSyuIm
+hdwUb6qzkGl55fP/bnA0e1b5FbIrSlTpbHl6PbG3qyaL2+TqxFNwq1Gkhw44xHex
+kDi44SFXRLOpKvHVHYoSo+2igg3QFdasJYpblfUhaQKBgF0l9PpMbDLdPlI33IQz
+bEzw0ig8R8nHocJzOkK/BdLI8WiItYGgq4mcZGDWsztNg17QQGTEFrH7H9B8DKAJ
+p75jz5O83arjMECqAiXlSGOWtq6NhbgyJcQJxvvvN8wObVFoVkLoEbqE1TkDqZfI
+CqiusA5zgG89vzP9xFhW2ia2
+-----END PRIVATE KEY-----
diff --git a/public/ssl/localhost+1.pem b/public/ssl/localhost+1.pem
new file mode 100644
index 0000000..332a3ae
--- /dev/null
+++ b/public/ssl/localhost+1.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEXzCCAsegAwIBAgIRAOLUY4uS9d2yXx0vd6qql30wDQYJKoZIhvcNAQELBQAw
+gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqQklM
+SUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHor7opMTowOAYDVQQD
+DDFta2NlcnQgQklMSUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHo
+r7opMB4XDTIyMDcyNzA4NDcyOFoXDTI0MTAyNzA4NDcyOFowXjEnMCUGA1UEChMe
+bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCpCSUxJQklM
+SVxnYW9zaHVhaXhpbmdAQ04yMTAxMDIyNzQgKOWNoeivuikwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDpWYqPkljVtDNpJVwlcOxNVihQPf4T3Q/tuIt5
+znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbYm6AJihkKy0iKj1mVS9TPCzqc
+HFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6ocWp6nw1gMhrMIxqT2MQIQX1
+6SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWndS4GpbT39rW9cmbJrGHCvl8b
+sm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAuls8p/lAoIvQy8MkLLM5b392C
+LMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP8/8a2bU/AgMBAAGjZDBiMA4G
+A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQn
+B1E5Js/cFhxBwpZL59aoK/skLjAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEw
+DQYJKoZIhvcNAQELBQADggGBAIaUncQj2XN2rNn6sE0MuaWboFqwpkydyei6FvtN
+c/TY9RWW3QRYICcO721l/2jBiWplQt/ZYaJ+IWN+C+3JSAz9IYsM/nMgxHL2azLQ
+zHKnASEjxptW9+mlsgVk2LTrBfbc197ikLu80M/0jQYaIBeoEOaMlhBjno139nTO
+evNheyFKvAhggOseD00I9VBZkKDBxvqr6PHnGjyAU43C1/HkNjglIbQjAZdBmXlX
+HuelQ97glfhzyApvmczPrc8IAqPhtYn2nJ5P6Ea35LEc3D7uVExywcjDFcSwMJCb
+TXqouzM/U8pO+DGeuvgwkYrBGlA7iEE+ZQgxCBatOXwG95THtFlfW+H0ILHB2tcX
+P+Kztwd+4ipPciJz+1NK7z7erwfxHO5hmXJskH9YWi6YJsIw5g1iYs0pJJ/4p7Bd
+8qSGEhri/+iijcC76q+1N0xhJxQrDDlC0pKp6oAYFDGKirzwmlAf/eJBy0ORWjCj
+yk+d9T622yzcXa5fw3HBZh1o6A==
+-----END CERTIFICATE-----
diff --git a/python/fastapi-demo.py b/python/fastapi-demo.py
new file mode 100644
index 0000000..586df9e
--- /dev/null
+++ b/python/fastapi-demo.py
@@ -0,0 +1,28 @@
+import argparse
+import uvicorn
+from fastapi import FastAPI
+
+app = FastAPI()
+
+# argparse
+parser = argparse.ArgumentParser(description='Process some integers.')
+parser.add_argument('--port', type=int, default=7074, help='The port number.')
+args = parser.parse_args()
+
+@app.get("/")
+async def index():
+ return {"message": "Hello World"}
+
+@app.get("/api/hello")
+async def hello():
+ return {
+ "app_name": "FastAPI框架学习",
+ "app_version": "v0.0.1"
+ }
+
+if __name__ == "__main__":
+ # uvicorn会多创建一个进程,并且stdio独立于控制台,如果(开发时)出现进程没有关闭,可尝试关闭终端
+ uvicorn.run(app, host="127.0.0.1", port=args.port)
+
+# 控制台默认关闭输出信息,如果想要查看控制台输出,请单独启动服务 npm run dev-python
+print("python server is running at port:", args.port)
\ No newline at end of file
diff --git a/python/main.py b/python/main.py
new file mode 100644
index 0000000..14e1e84
--- /dev/null
+++ b/python/main.py
@@ -0,0 +1,57 @@
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+import argparse
+import signal
+import sys
+
+# flask-demo
+
+# argparse
+parser = argparse.ArgumentParser(description='Process some integers.')
+parser.add_argument('--port', type=int, default=7074, help='The port number.')
+args = parser.parse_args()
+
+app = Flask(__name__)
+
+# 配置 CORS,允许所有来源
+CORS(app)
+
+# 定义路由和处理器
+@app.route('/', methods=['GET'])
+def index():
+ name = request.args.get('name', 'World')
+ return jsonify({'message': f'Hello, {name}!'}), 200
+
+
+@app.route('/api/hello', methods=['GET'])
+def hello():
+ name = request.args.get('name', 'World')
+ return jsonify({'message': f'Hello, {name}!'}), 200
+
+# 通过信号来退出服务,否则会出现终端显示退出后,实际进程仍在运行
+# 定义信号处理函数
+def signal_handler(sig, frame):
+ print("[python] [flask] Received signal to terminate the server:", sig)
+ sys.exit(0)
+
+ # 关闭 Flask 应用
+ # func = request.environ.get('werkzeug.server.shutdown')
+ # if func is None:
+ # func = lambda: None
+ # func()
+
+ # 退出主线程
+ # threading.main_thread().exit()
+
+# 注册信号处理函数
+signal.signal(signal.SIGTERM, signal_handler)
+signal.signal(signal.SIGINT, signal_handler)
+
+if __name__ == '__main__':
+ # 以api方式启动服务会出现警告,请忽略
+ app.run(port=args.port)
+
+# 或许flask内置的stdio与node.js stdio有冲突,导致控制台无法显示信息。
+# 如果想要查看控制台输出,请单独启动服务 npm run dev-python
+print("python server is running at port:", args.port)
+
diff --git a/python/requirements.txt b/python/requirements.txt
new file mode 100644
index 0000000..4c606c6
--- /dev/null
+++ b/python/requirements.txt
@@ -0,0 +1,2 @@
+Flask==3.0.2
+Flask_Cors==4.0.0
diff --git a/python/setup.py b/python/setup.py
new file mode 100644
index 0000000..cde3333
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,24 @@
+from cx_Freeze import setup, Executable
+
+# 创建可执行文件的配置
+executableApp = Executable(
+ script="main.py",
+ target_name="pyapp",
+)
+
+# 打包的参数配置
+options = {
+ "build_exe": {
+ "build_exe":"./dist/",
+ "excludes": ["*.txt"],
+ "optimize": 2,
+ }
+}
+
+setup(
+ name="pyapp",
+ version="1.0",
+ description="python app",
+ options=options,
+ executables=[executableApp]
+)
\ No newline at end of file