Browse Source

Merge branch 'master' into dev

# Conflicts:
#	electron/storage.js
gaoshuaixing 4 years ago
parent
commit
ff8daf7a20
79 changed files with 1934 additions and 155 deletions
  1. 1 0
      .gitignore
  2. 37 11
      README.md
  3. 0 17
      app.js
  4. 7 0
      app/const/storageKey.js
  5. 127 0
      app/controller/v1/example.js
  6. 38 19
      app/controller/v1/home.js
  7. 43 0
      app/controller/v1/setting.js
  8. 1 0
      app/public/css/app.2c9f504d.css
  9. 1 0
      app/public/css/chunk-85272794.56abef7d.css
  10. 7 0
      app/public/css/chunk-vendors.a1538f74.css
  11. BIN
      app/public/favicon.ico
  12. BIN
      app/public/images/tray_logo.png
  13. 1 0
      app/public/index.html
  14. 0 0
      app/public/js/app.e8c8fa84.js
  15. 0 0
      app/public/js/app.e8c8fa84.js.map
  16. 2 0
      app/public/js/chunk-40561e72.58ad986f.js
  17. 0 0
      app/public/js/chunk-40561e72.58ad986f.js.map
  18. 0 0
      app/public/js/chunk-798c13e2.24c4b750.js
  19. 0 0
      app/public/js/chunk-798c13e2.24c4b750.js.map
  20. 0 0
      app/public/js/chunk-85272794.20d8c8f3.js
  21. 0 0
      app/public/js/chunk-85272794.20d8c8f3.js.map
  22. 4 0
      app/public/js/chunk-vendors.3d275796.js
  23. 0 0
      app/public/js/chunk-vendors.3d275796.js.map
  24. 0 22
      app/public/loading.html
  25. 22 0
      app/router/example.js
  26. 9 0
      app/router/index.js
  27. 14 0
      app/router/setting.js
  28. 54 1
      app/service/base.js
  29. 52 0
      app/service/example.js
  30. 26 0
      app/service/setting.js
  31. 36 0
      app/service/socket.js
  32. 119 0
      app/service/storage.js
  33. 11 0
      app/view/hello.ejs
  34. 1 10
      app/view/index.ejs
  35. BIN
      build/img/open_dir.png
  36. BIN
      build/img/tmp-phone-home.png
  37. BIN
      build/img/upload_pic.png
  38. 0 6
      config/config.beta.js
  39. 6 0
      config/config.default.js
  40. 2 1
      config/config.local.js
  41. 0 7
      config/config.preview.js
  42. 2 1
      config/config.prod.js
  43. 102 0
      electron/api.js
  44. 21 0
      electron/apis/base.js
  45. 28 0
      electron/apis/example.js
  46. 29 6
      electron/config.js
  47. 28 0
      electron/lib/AutoLaunch.js
  48. 10 0
      electron/lib/Constant.js
  49. 9 2
      electron/setup.js
  50. 32 11
      electron/storage.js
  51. 38 0
      electron/tray.js
  52. 3 0
      frontend/.env
  53. 3 0
      frontend/.env.development
  54. 3 0
      frontend/.env.preview
  55. 23 0
      frontend/.gitignore
  56. 24 0
      frontend/README.md
  57. 5 0
      frontend/babel.config.js
  58. 50 0
      frontend/package.json
  59. BIN
      frontend/public/favicon.ico
  60. 17 0
      frontend/public/index.html
  61. 47 0
      frontend/src/App.vue
  62. 55 0
      frontend/src/api/main.js
  63. BIN
      frontend/src/assets/logo.png
  64. 58 0
      frontend/src/components/HelloWorld.vue
  65. 27 0
      frontend/src/config/router.config.js
  66. 17 0
      frontend/src/main.js
  67. 17 0
      frontend/src/router/index.js
  68. 35 0
      frontend/src/utils/axios.js
  69. 61 0
      frontend/src/utils/request.js
  70. 68 0
      frontend/src/utils/util.js
  71. 169 0
      frontend/src/views/Layout.vue
  72. 77 0
      frontend/src/views/Setting.vue
  73. 62 0
      frontend/src/views/file/OpenDir.vue
  74. 80 0
      frontend/src/views/file/UploadFile.vue
  75. 11 0
      frontend/vue.config.js
  76. 60 32
      main.js
  77. 12 7
      package.json
  78. 0 2
      storage/.gitignore
  79. 30 0
      tools/replace_dist.js

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@ out/
 logs/
 run/
 .idea/
+package-lock.json

+ 37 - 11
README.md

@@ -1,9 +1,9 @@
-# electron-egg
-一个简单、快速、功能丰富的桌面软件开发框架,基于electron和egg.js
+# electron-egg 
+一个简单、快速、功能丰富的跨平台桌面软件开发框架。
 
-[官网: http://b.kaka996.com/](http://b.kaka996.com/)
+查看:[教程文档](https://www.yuque.com/u34495/mivcfg/xnhmms)
 
-[文档迁移到了羽雀](https://www.yuque.com/u34495/mivcfg/xnhmms)
+🏆 码云最有价值开源项目
 
 ## 特性
 1. 直接打包成windows版、Mac版、Linux版或者以web网站运行。
@@ -13,6 +13,13 @@
 5. 桌面软件常见功能,后续逐步集成并完善或提供demo。
 6. 软件自动更新。
 
+## 默认UI
+
+- 使用vue编写,经典三栏样式,可自定义
+
+![](./build/img/upload_pic.png)
+![](./build/img/open_dir.png)
+
 ## 开始使用
 
 1. 下载
@@ -23,12 +30,14 @@
     git clone https://github.com/wallace5303/electron-egg.git
     ```
 
-2. 启动
+2. 安装
     ```
+    # 提升安装速度,使用国内镜像;注:勿使用cnpm命令,可能出现安装不完整
+    npm config set registry https://registry.npm.taobao.org
     # 进入目录 ./electron-egg/
     npm install
-    npm run dev
     ```
+    
 3. 常用命令
     ```
     # 开发者模式
@@ -56,18 +65,35 @@
     npm run web-stop
     ```
 
-## 项目demo
+## 项目案例
+
+1. [小明云存储](https://gitee.com/wallace5303/xm-pic)
+
+![](https://cdn.jsdelivr.net/gh/wallace5303/file-resource/normal/xm-pic-config.png)
+
+![](https://cdn.jsdelivr.net/gh/wallace5303/file-resource/normal/xm-pic-provider.png)
+
+![](https://cdn.jsdelivr.net/gh/wallace5303/file-resource/normal/xm-pic-history.png)
+
+![](https://cdn.jsdelivr.net/gh/wallace5303/file-resource/normal/xm-pic-detail.png)
+
 
-1. 网址管理大师(开源)
-![](https://i.loli.net/2020/11/02/ByFDKeY6nmdxGoc.png)
-    [地址](https://gitee.com/wallace5303/box)
 
 ## 交流
 1. qq群:735532437
 2. 把一些常用或者重复的功能,做成桌面小工具,给自己或者别人用,确实省了不少时间^_^ 
 
 ## 进行中功能
-1. 软件自动更新(已完成)
+
+1. 软件自动更新(已完成)
+2. 数据本地存储(已完成)
+3. mac版功能兼容(已完成)
+4. 以web版运行(已完成)
+5. 默认UI(已完成)
+6. egg与electron通信(已完成)
+7. 打开文件功能demo(已完成)
+8. 上传文件到sm图床demo(已完成)
+9. 开发模式实时渲染页面(已完成)
 
 ## 欢迎star
 

+ 0 - 17
app.js

@@ -4,11 +4,6 @@
  */
 'use strict';
 global.CODE = require('./app/const/statusCode');
-const fs = require('fs');
-const path = require('path');
-const lowdb = require('lowdb');
-const FileSync = require('lowdb/adapters/FileSync');
-const utils = require('./app/utils/utils');
 
 class AppBootHook {
   constructor(app) {
@@ -38,18 +33,6 @@ class AppBootHook {
   async didReady() {
     // Worker is ready, can do some things
     // don't need to block the app boot.
-    // 数据库
-    const storageDir = path.normalize('./storage/');
-    if (!fs.existsSync(storageDir)) {
-      utils.mkdir(storageDir);
-      utils.chmodPath(storageDir, '777');
-    }
-    const file = storageDir + 'db.json';
-    const adapter = new FileSync(file);
-    const db = lowdb(adapter);
-    if (!db.has('default').value()) {
-      db.set('default', {}).write();
-    }
   }
 
   async serverDidReady() {

+ 7 - 0
app/const/storageKey.js

@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = {
+  EGG_CONFIG: 'egg_config',
+  ELECTRON_IPC: 'electron_ipc',
+  TEST_DATA: 'test_data'
+};

+ 127 - 0
app/controller/v1/example.js

@@ -0,0 +1,127 @@
+'use strict';
+
+const BaseController = require('../base'); 
+const os = require('os');
+const fs = require('fs');
+const path = require('path');
+
+class ExampleController extends BaseController {
+
+  async openLocalDir() {
+    const self = this;
+    const { ctx, service } = this;
+    const body = ctx.request.body;
+    const id = body.id;
+    const data = {};
+    let dir = '';
+    switch (id) {
+      case 'download' :
+        dir = os.userInfo().homedir + '/Downloads';
+        break;
+      case 'picture' :
+        dir = os.userInfo().homedir + '/Pictures';
+        break;    
+      case 'doc' :
+        dir = os.userInfo().homedir + '/Documents';
+        break;      
+      case 'music' :
+        dir = os.userInfo().homedir + '/Music';
+        break;    
+    }
+
+    await service.example.openLocalDir(dir);
+
+    self.sendSuccess(data);
+  }
+
+  async uploadFile() {
+    const self = this;
+    const { ctx, service } = this;
+    let tmpDir = service.storage.getStorageDir();
+    // for (const file of ctx.request.files) {
+    //   this.app.logger.info('file:', file);
+
+    //   try {
+    //     let tmpFile = fs.readFileSync(file.filepath) 
+    //     fs.writeFileSync(path.join(tmpDir, file.filename), tmpFile)
+    //   } finally {
+    //     await fs.unlink(file.filepath, function(){});
+    //   }
+    //   const fileStream = fs.createReadStream(path.join(tmpDir, file.filename)) 
+    //   const uploadRes = await service.example.uploadFileToSMMS(fileStream);
+    // }
+    const file = ctx.request.files[0];
+    //this.app.logger.info('file:', file);
+
+    try {
+      let tmpFile = fs.readFileSync(file.filepath) 
+      fs.writeFileSync(path.join(tmpDir, file.filename), tmpFile)
+    } finally {
+      await fs.unlink(file.filepath, function(){});
+    }
+    const fileStream = fs.createReadStream(path.join(tmpDir, file.filename)) 
+    const uploadRes = await service.example.uploadFileToSMMS(fileStream);
+
+    self.sendData(uploadRes);
+  }
+
+  async getWsUrl() {
+    const self = this;
+    const { service } = this;
+    const data = {};
+
+    const addr = await service.socket.getWsUrl();
+    data.url = addr;
+
+    self.sendSuccess(data);
+  }
+
+  async addTestData() {
+    const self = this;
+    const { service } = this;
+    const data = {};
+
+    const userInfo = {
+      name: 'jame',
+      age: 18,
+      gender: 'man'
+    }
+    await service.storage.addTestData(userInfo);
+
+    self.sendSuccess(data);
+  }
+
+  async delTestData() {
+    const self = this;
+    const { service } = this;
+    const data = {};
+    const name = 'jame';
+    await service.storage.delTestData(name);
+
+    self.sendSuccess(data);
+  }
+
+  async updateTestData() {
+    const self = this;
+    const { service } = this;
+    const data = {};
+    const name = 'jame';
+    const age = 20;
+    await service.storage.updateTestData(name, age);
+
+    self.sendSuccess(data);
+  }
+
+  async getTestData() {
+    const self = this;
+    const { service } = this;
+    const data = {};
+    const name = 'jame';
+    const user = await service.storage.getTestData(name);
+    data.user = user;
+
+    self.sendSuccess(data);
+  }
+}
+
+module.exports = ExampleController;

+ 38 - 19
app/controller/v1/home.js

@@ -1,19 +1,38 @@
-'use strict';
-
-const BaseController = require('../base');
-
-class HomeController extends BaseController {
-
-  async index() {
-    const { ctx } = this;
-
-    const data = {
-      title: 'hello electron-egg'
-    };
-
-    await ctx.render('index.ejs', data);
-  }
-  
-}
-
-module.exports = HomeController;
+'use strict';
+
+const BaseController = require('../base');
+
+class HomeController extends BaseController {
+
+  async index() {
+    const { ctx } = this;
+
+    const data = {
+      title: 'hello electron-egg'
+    };
+
+    await ctx.render('index.ejs', data);
+  }
+  
+  async hello() {
+    const { ctx } = this;
+
+    const data = {
+      title: 'hello'
+    };
+
+    this.sendSuccess(data);
+  }
+
+  async helloPage() {
+    const { ctx } = this;
+
+    const data = {
+      title: 'hello'
+    };
+
+    await ctx.render('hello.ejs', data);
+  }
+}
+
+module.exports = HomeController;

+ 43 - 0
app/controller/v1/setting.js

@@ -0,0 +1,43 @@
+'use strict';
+
+const BaseController = require('../base');
+
+class SettingController extends BaseController {
+
+  async autoLaunchEnable() {
+    const { service } = this;
+
+    await service.setting.autoLaunchEnable();
+    const data = {
+      isEnabled: true
+    };
+
+    this.sendSuccess(data);
+  }
+  
+  async autoLaunchDisable() {
+    const { service } = this;
+    
+    await service.setting.autoLaunchDisable();
+    const data = {
+      isEnabled: false
+    };
+
+    this.sendSuccess(data);
+  }
+
+  async autoLaunchIsEnabled() {
+    const { service } = this;
+
+    const data = {
+      isEnabled: null
+    };
+
+    const isEnabled = await service.setting.autoLaunchIsEnabled();
+    data.isEnabled = isEnabled;
+
+    this.sendSuccess(data);
+  }
+}
+
+module.exports = SettingController;

+ 1 - 0
app/public/css/app.2c9f504d.css

@@ -0,0 +1 @@
+#components-layout-demo-responsive .logo[data-v-e0a55cae]{border-bottom:1px solid #e8e8e8}#components-layout-demo-responsive .pic-logo[data-v-e0a55cae]{height:32px;margin:10px}#components-layout-demo-responsive .layout-sider[data-v-e0a55cae]{border-right:1px solid #e8e8e8}#components-layout-demo-responsive .menu-item .ant-menu-item[data-v-e0a55cae]{background-color:#fff;margin-top:0;margin-bottom:0}#components-layout-demo-responsive .sub-layout-sider[data-v-e0a55cae]{background-color:#fafafa}#components-layout-demo-responsive .sub-menu-item .ant-menu-item[data-v-e0a55cae]{margin-top:0;margin-bottom:0}#components-layout-demo-responsive .sub-menu-item .ant-menu-item[data-v-e0a55cae]:after{border-right:3px solid #f2f2f2}#components-layout-demo-responsive .sub-menu-item .ant-menu-item-selected[data-v-e0a55cae]{background-color:#f2f2f2}#components-layout-demo-responsive .sub-menu-item .ant-menu-item-selected span[data-v-e0a55cae]{color:#111}#components-layout-demo-responsive .sub-menu-item.ant-menu[data-v-e0a55cae]{background:#fafafa}#components-layout-demo-responsive .sub-menu-item.ant-menu-inline[data-v-e0a55cae]{border-right:0 solid #fafafa}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}

+ 1 - 0
app/public/css/chunk-85272794.56abef7d.css

@@ -0,0 +1 @@
+#set .ant-list-item[data-v-2c7c633a]:last-child{border-bottom:1px solid #e8e8e8}

File diff suppressed because it is too large
+ 7 - 0
app/public/css/chunk-vendors.a1538f74.css


BIN
app/public/favicon.ico


BIN
app/public/images/tray_logo.png


+ 1 - 0
app/public/index.html

@@ -0,0 +1 @@
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>electron-egg</title><link href="/css/chunk-85272794.56abef7d.css" rel="prefetch"><link href="/js/chunk-40561e72.58ad986f.js" rel="prefetch"><link href="/js/chunk-798c13e2.24c4b750.js" rel="prefetch"><link href="/js/chunk-85272794.20d8c8f3.js" rel="prefetch"><link href="/css/app.2c9f504d.css" rel="preload" as="style"><link href="/css/chunk-vendors.a1538f74.css" rel="preload" as="style"><link href="/js/app.e8c8fa84.js" rel="preload" as="script"><link href="/js/chunk-vendors.3d275796.js" rel="preload" as="script"><link href="/css/chunk-vendors.a1538f74.css" rel="stylesheet"><link href="/css/app.2c9f504d.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but electron-egg doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.3d275796.js"></script><script src="/js/app.e8c8fa84.js"></script></body></html>

File diff suppressed because it is too large
+ 0 - 0
app/public/js/app.e8c8fa84.js


File diff suppressed because it is too large
+ 0 - 0
app/public/js/app.e8c8fa84.js.map


+ 2 - 0
app/public/js/chunk-40561e72.58ad986f.js

@@ -0,0 +1,2 @@
+(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-40561e72"],{a358:function(t,n,e){"use strict";e.d(n,"a",(function(){return i})),e.d(n,"b",(function(){return u}));var o=e("b775"),a={outApi:"/api/v1/outApi",openDir:"/api/v1/example/openLocalDir",uploadFile:"/api/v1/example/uploadFile",autoLaunchEnable:"/api/v1/setting/autoLaunchEnable",autoLaunchDisable:"/api/v1/setting/autoLaunchDisable",autoLaunchIsEnabled:"/api/v1/setting/autoLaunchIsEnabled"};function i(t,n){return Object(o["b"])({url:a[t],method:"post",data:n})}function u(t){return Object(o["b"])({url:a.openDir,method:"post",data:t})}},a778:function(t,n,e){"use strict";e.r(n);var o=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",[e("h3",{style:{marginBottom:"16px"}},[t._v(" demo1 打开文件夹实现 ")]),e("a-list",{attrs:{bordered:"","data-source":t.data},scopedSlots:t._u([{key:"renderItem",fn:function(n){return e("a-list-item",{on:{click:function(e){return t.openDirectry(n.id)}}},[t._v(" "+t._s(n.content)+" "),e("a-button",{attrs:{type:"link"}},[t._v(" 打开 ")])],1)}}])})],1)},a=[],i=e("a358"),u=[{content:"【下载】目录",id:"download"},{content:"【图片】目录",id:"picture"},{content:"【文档】目录",id:"doc"},{content:"【音乐】目录",id:"music"}],c={data:function(){return{data:u}},methods:{openDirectry:function(t){console.log("id:",t);var n={id:t};Object(i["b"])(n).then((function(t){if(0!==t.code)return!1})).catch((function(t){console.log("err:",t)}))}}},r=c,d=e("2877"),l=Object(d["a"])(r,o,a,!1,null,null,null);n["default"]=l.exports}}]);
+//# sourceMappingURL=chunk-40561e72.58ad986f.js.map

File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-40561e72.58ad986f.js.map


File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-798c13e2.24c4b750.js


File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-798c13e2.24c4b750.js.map


File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-85272794.20d8c8f3.js


File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-85272794.20d8c8f3.js.map


File diff suppressed because it is too large
+ 4 - 0
app/public/js/chunk-vendors.3d275796.js


File diff suppressed because it is too large
+ 0 - 0
app/public/js/chunk-vendors.3d275796.js.map


+ 0 - 22
app/public/loading.html

@@ -1,22 +0,0 @@
-<html>
- <head> 
-  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
-  <style type="text/css">
-    body{
-      margin:0px auto;
-    }
-    #picture1 {
-      position: absolute;
-      left: 50%;
-      top: 50%;
-      transform: translate(-50%, -50%);
-    }
-  </style> 
-  <title></title> 
- </head> 
- <body> 
-  <div id="picture1">
-   <img src="./images/loding.gif" />
-  </div>  
- </body>
-</html>

+ 22 - 0
app/router/example.js

@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  // open local dir
+  router.post('/api/v1/example/openLocalDir', controller.v1.example.openLocalDir);
+  // upload file
+  router.post('/api/v1/example/uploadFile', controller.v1.example.uploadFile);
+  // get ws url
+  router.post('/api/v1/example/getWsUrl', controller.v1.example.getWsUrl);
+  // add test data
+  router.post('/api/v1/example/addTestData', controller.v1.example.addTestData);
+  // delete test data
+  router.post('/api/v1/example/delTestData', controller.v1.example.delTestData);
+  // update test data
+  router.post('/api/v1/example/updateTestData', controller.v1.example.updateTestData);
+  // get test data
+  router.post('/api/v1/example/getTestData', controller.v1.example.getTestData);
+};

+ 9 - 0
app/router/index.js

@@ -5,7 +5,16 @@
  */
 module.exports = app => {
   const { router, controller } = app;
+  // home
   router.get('/', controller.v1.home.index);
+
+  // hello
+  //router.get('/', controller.v1.home.hello);
+
   // html
   router.get('/home', controller.v1.home.index);
+
+  // 引入其他路由
+  require('./example')(app);
+  require('./setting')(app);
 };

+ 14 - 0
app/router/setting.js

@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  // open launch
+  router.post('/api/v1/setting/autoLaunchEnable', controller.v1.setting.autoLaunchEnable);
+  // close launch 
+  router.post('/api/v1/setting/autoLaunchDisable', controller.v1.setting.autoLaunchDisable);
+  // is launch 
+  router.post('/api/v1/setting/autoLaunchIsEnabled', controller.v1.setting.autoLaunchIsEnabled);
+};

+ 54 - 1
app/service/base.js

@@ -3,7 +3,60 @@
 const Service = require('egg').Service;
 
 class BaseService extends Service {
-  // base
+
+  /*
+   * ipc call
+   */
+  async ipcCall(method = '', ...params) {
+    let result = {
+      err: null,
+      data: null
+    };
+    if (!method) {
+      result.err = 'Method does not exist';
+      return result;
+    }
+
+    try {
+      result = await this.service.socket.call(method, params);
+    } catch (err) {
+      this.app.logger.error('[base] [ipcCall] request error:', err);
+      result.err = 'request err';
+    }
+    this.app.logger.info('[base] [ipcCall] result:', result);
+
+    return result;
+  }
+
+  /*
+   * ipc call
+   */
+  // async ipcCall(method = '', ...params) {
+  //   let result = {
+  //     err: null,
+  //     data: null
+  //   };
+  //   if (!method) {
+  //     result.err = 'Method does not exist';
+  //     return result;
+  //   }
+
+  //   const port = this.service.storage.getElectronIPCPort();
+  //   const url  = 'http://localhost:' + port + '/send';
+  //   try {
+  //     const response = await request.post(url)
+  //       .send({ cmd: method, params: params })
+  //       .set('accept', 'json');
+        
+  //       result = JSON.parse(response.text);  
+  //   } catch (err) {
+  //     this.app.logger.error('[base] [ipcCall] request error:', err);
+  //     result.err = 'request err';
+  //   }
+  //   this.app.logger.info('[base] [ipcCall] result:', result);
+
+  //   return result;
+  // }
 }
 
 module.exports = BaseService;

+ 52 - 0
app/service/example.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const BaseService = require('./base');
+
+class ExampleService extends BaseService {
+  async openLocalDir(dir) {
+    const self = this;
+
+    await self.ipcCall('example.openDir', dir);
+
+    return true;
+  }
+
+  async uploadFileToSMMS(tmpFile) {
+    const res = {
+      code: 1000,
+      message: 'unknown error',
+    };
+
+    try {
+      //throw new Error('Sync Error');
+      const headersObj = {
+        'Content-Type': 'multipart/form-data',
+        'Authorization': 'pHVaIfVX8kgxsEL2THTYMVzJDYY3MMZU'
+      };
+      const url = 'https://sm.ms/api/v2/upload';
+      const response = await this.app.curl(url, {
+        method: 'POST',
+        headers: headersObj,
+        files: {
+          smfile: tmpFile,
+        },
+        dataType: 'json',
+        timeout: 15000,
+      });
+      const result = response.data;
+      if (this.app.config.env === 'local') {
+        this.app.logger.info('[ExampleService] [uploadFileToSMMS]: info result:%j', result);
+      }
+      if (result.code !== 'success') {
+        this.app.logger.error('[ExampleService] [uploadFileToSMMS]: res error result:%j', result);
+      }
+      return result;
+    } catch (e) {
+      this.app.logger.error('[ExampleService] [uploadFileToSMMS]:  ERROR ', e);
+    }
+
+    return res;
+  }
+}
+
+module.exports = ExampleService;

+ 26 - 0
app/service/setting.js

@@ -0,0 +1,26 @@
+'use strict';
+
+const BaseService = require('./base');
+
+class SettingService extends BaseService {
+
+  async autoLaunchEnable() {
+    const callResult = await this.ipcCall('base.autoLaunchEnable');
+
+    return callResult.data;
+  }
+
+  async autoLaunchDisable() {
+    const callResult = await this.ipcCall('base.autoLaunchDisable');
+
+    return callResult.data;
+  }
+
+  async autoLaunchIsEnabled() {
+    const callResult = await this.ipcCall('base.autoLaunchIsEnabled');
+
+    return callResult.data;
+  }
+}
+
+module.exports = SettingService;

+ 36 - 0
app/service/socket.js

@@ -0,0 +1,36 @@
+'use strict';
+
+const BaseService = require('./base');
+const io = require('socket.io-client');
+
+this.instance = null;
+
+class SocketService extends BaseService {
+
+  instance() {
+    if (!SocketService.instance) {
+      const port = this.service.storage.getElectronIPCPort();
+      const url  = 'http://localhost:' + port;
+      const instance = io(url);
+      SocketService.instance = instance;
+    }
+    return SocketService.instance;
+  }
+
+  call (method = '', params) {
+    return new Promise((resolve, reject) => {
+      this.instance().emit('ipc', { cmd: method, params: params }, (response) => {
+        resolve(response);
+      });
+    });
+  }
+
+  async getWsUrl () {
+    const port = this.service.storage.getElectronIPCPort();
+    const url  = 'http://localhost:' + port;
+
+    return url;
+  }
+}
+
+module.exports = SocketService;

+ 119 - 0
app/service/storage.js

@@ -0,0 +1,119 @@
+'use strict';
+
+const BaseService = require('./base');
+const path = require('path');
+const _ = require('lodash');
+const lowdb = require('lowdb');
+const FileSync = require('lowdb/adapters/FileSync');
+const storageKey = require('../const/storageKey');
+const fs = require('fs');
+const os = require('os');
+const utils = require('../utils/utils');
+const pkg = require('../../package.json');
+const storageDb = 'db.json';
+
+class StorageService extends BaseService {
+  /*
+   * instance
+   */
+  instance(file = null) {
+    if (!file) {
+      const storageDir = this.getStorageDir();
+      if (!fs.existsSync(storageDir)) {
+        utils.mkdir(storageDir);
+        utils.chmodPath(storageDir, '777');
+      }
+      file = path.normalize(storageDir + storageDb);
+    }
+    const isExist = fs.existsSync(file);
+    if (!isExist) {
+        return null;
+    }
+  
+    const adapter = new FileSync(file);
+    const db = lowdb(adapter);
+  
+    return db;
+  }
+
+  /*
+   * getElectronIPCPort
+   */
+  getElectronIPCPort() {
+    const key = storageKey.ELECTRON_IPC + '.port';
+    const port = this.instance()
+    .get(key)
+    .value();
+  
+    return port;
+  }
+
+  /*
+   * getStorageDir
+   */
+  getStorageDir() {
+    const userHomeDir = os.userInfo().homedir;
+    const storageDir = path.normalize(userHomeDir + '/' + pkg.name + '/');
+
+    return storageDir;
+  }
+
+  /*
+   * add Test data
+   */
+  async addTestData(user) {
+    const key = storageKey.TEST_DATA;
+    if (!this.instance().has(key).value()) {
+      this.instance().set(key, []).write();
+    }
+    
+    const data = this.instance()
+    .get(key)
+    .push(user)
+    .write();
+
+    return data;
+  }
+
+  /*
+   * del Test data
+   */
+  async delTestData(name = '') {
+    const key = storageKey.TEST_DATA;
+    const data = this.instance()
+    .get(key)
+    .remove({name: name})
+    .write();
+
+    return data;
+  }
+
+  /*
+   * update Test data
+   */
+  async updateTestData(name= '', age = 0) {
+    const key = storageKey.TEST_DATA;
+    const data = this.instance()
+    .get(key)
+    .find({name: name})
+    .assign({ age: age})
+    .write();
+
+    return data;
+  }
+
+  /*
+   * get Test data
+   */
+  async getTestData(name = '') {
+    const key = storageKey.TEST_DATA;
+    const data = this.instance()
+    .get(key)
+    .find({name: name})
+    .value();
+
+    return data;
+  }
+}
+
+module.exports = StorageService;

+ 11 - 0
app/view/hello.ejs

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title><%= title %></title>
+    <link rel='stylesheet' href='/stylesheets/style.css' />
+  </head>
+  <body>
+    <h1><%= title %></h1>
+    <p>Welcome to electron-egg</p>
+  </body>
+</html>

+ 1 - 10
app/view/index.ejs

@@ -1,10 +1 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title><%= title %></title>
-    <link rel='stylesheet' href='/stylesheets/style.css' />
-  </head>
-  <body>
-    <h1><%= title %></h1>
-  </body>
-</html>
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>electron-egg</title><link href="/css/chunk-85272794.56abef7d.css" rel="prefetch"><link href="/js/chunk-40561e72.58ad986f.js" rel="prefetch"><link href="/js/chunk-798c13e2.24c4b750.js" rel="prefetch"><link href="/js/chunk-85272794.20d8c8f3.js" rel="prefetch"><link href="/css/app.2c9f504d.css" rel="preload" as="style"><link href="/css/chunk-vendors.a1538f74.css" rel="preload" as="style"><link href="/js/app.e8c8fa84.js" rel="preload" as="script"><link href="/js/chunk-vendors.3d275796.js" rel="preload" as="script"><link href="/css/chunk-vendors.a1538f74.css" rel="stylesheet"><link href="/css/app.2c9f504d.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but electron-egg doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.3d275796.js"></script><script src="/js/app.e8c8fa84.js"></script></body></html>

BIN
build/img/open_dir.png


BIN
build/img/tmp-phone-home.png


BIN
build/img/upload_pic.png


+ 0 - 6
config/config.beta.js

@@ -1,6 +0,0 @@
-'use strict';
-// 本地环境-配置文件
-
-exports.logger = {
-  dir: './logs/beta',
-};

+ 6 - 0
config/config.default.js

@@ -91,6 +91,12 @@ module.exports = appInfo => {
   };
 
   config.ejs = {};
+
+  config.multipart = {
+    mode: 'file',
+    fileExtensions: [ '.xlsx' ] // 增加你需要的文件扩展名
+  };
+
   return {
     ...config,
     ...userConfig,

+ 2 - 1
config/config.local.js

@@ -1,5 +1,6 @@
 'use strict';
 // 本地环境-配置文件
+const storageDir = require('../electron/storage').getStorageDir();
 
 /*
  * 远程调用
@@ -8,5 +9,5 @@ exports.outApi = {
   login: 'http://local.com/api/login',
 };
 exports.logger = {
-  dir: './logs/local',
+  dir: storageDir + 'logs/local',
 };

+ 0 - 7
config/config.preview.js

@@ -1,7 +0,0 @@
-'use strict';
-// 本地环境-配置文件
-
-exports.logger = {
-  dir: './logs/preview',
-};
-

+ 2 - 1
config/config.prod.js

@@ -1,5 +1,6 @@
 'use strict';
 // 本地环境-配置文件
+const storageDir = require('../electron/storage').getStorageDir();
 
 /*
  * 远程调用
@@ -8,6 +9,6 @@ exports.outApi = {
   login: 'http://api.local.com/api/login',
 };
 exports.logger = {
-  dir: './logs/prod',
+  dir: storageDir + 'logs/prod',
 };
 

+ 102 - 0
electron/api.js

@@ -0,0 +1,102 @@
+'use strict';
+const fs = require('fs');
+const http = require('http');
+const path = require('path');
+const _ = require('lodash');
+const storage = require('./storage');
+const socketIo = require('socket.io');
+
+const apis = {};
+
+exports.setup = async function () {
+  setApi();
+
+  // use api server
+  let port = await storage.setIpcDynamicPort();
+  ELog.info('[api] [setup] dynamic port:', port);
+  const listen = 'localhost';
+  port = port ? port : 7069;
+
+  const server = http.createServer(function(req, res) {
+    ELog.info('[ api ] [setup] command received', { method: req.method, url: req.url });
+    if ((req.method === 'POST' && req.url === '/send')) {
+      let body = '';
+      req.setEncoding('utf8');
+      req
+      .on('data', function(data) {
+        body += data;
+      })
+      .on('end', function() {
+        let message;
+        try {
+          message = JSON.parse(body);
+        } catch (e) {
+          res.statusCode = 400;
+          return res.end('request body parse failure.');
+        }
+        if (!apis[message.cmd]) {
+          ELog.info('[ api ] [setup] invalid command called:', message.cmd);
+          res.statusCode = 404;
+          return res.end('NG');
+        }
+
+        ELog.info('[ api ] [setup] command received message:', message);
+        const data = apis[message.cmd](...message.params);
+        res.statusCode = 200;
+        const result = {
+          err: null,
+          data: data,
+        };
+        res.end(JSON.stringify(result));
+      });
+    } else {
+      res.statusCode = 404;
+      res.end('NOT FOUND');
+    }
+  });
+
+  // socket io
+  const io = socketIo(server);
+  io.on('connection', (socket) => {
+    socket.on('ipc', (message, callback) => {
+      ELog.info('[ api ] [setup] socket id:' + socket.id + ' message cmd: ' + message.cmd);
+      const data = apis[message.cmd](...message.params);
+      const result = {
+        err: null,
+        data: data,
+      };
+      callback(result);
+    });
+  });
+
+  server.listen(port, listen, function() {
+    ELog.info('[ api ] [setup] server is listening on', `${listen}:${port}`);
+  });  
+};
+
+function setApi() {
+  const apiDir = path.normalize(__dirname + '/apis');
+  // console.log('apiDir:', apiDir);
+  fs.readdirSync(apiDir).forEach(function(filename) {
+    if (path.extname(filename) === '.js' && filename !== 'index.js') {
+      const name = path.basename(filename, '.js');
+      const fileObj = require(`./apis/${filename}`);
+      _.map(fileObj, function(fn, method) {
+        let methodName = getApiName(name, method);
+        apis[methodName] = fn;
+        // ELog.info('[ Monitor ]', methodName);
+      });
+    }
+  });
+};
+
+/**
+ * get api method name
+ * ex.) jsname='user' method='get' => 'user.get'
+ * @param {String} jsname
+ * @param {String} method
+ */
+function getApiName (jsname, method) {
+  return jsname + '.' + method;
+  //return jsname + method.charAt(0).toUpperCase() + method.slice(1);
+};

+ 21 - 0
electron/apis/base.js

@@ -0,0 +1,21 @@
+'use strict';
+
+const AutoLaunchManager = require('../lib/AutoLaunch');
+
+exports.autoLaunchEnable = function () {
+  const autoLaunchManager = new AutoLaunchManager();
+  const enable = autoLaunchManager.enable();
+  return enable;
+}
+
+exports.autoLaunchDisable = function () {
+  const autoLaunchManager = new AutoLaunchManager();
+  const disable = autoLaunchManager.disable();
+  return disable;
+}
+
+exports.autoLaunchIsEnabled = function () {
+  const autoLaunchManager = new AutoLaunchManager();
+  const isEnable = autoLaunchManager.isEnabled();
+  return isEnable;
+}

+ 28 - 0
electron/apis/example.js

@@ -0,0 +1,28 @@
+'use strict';
+
+const path = require('path');
+const { app, shell } = require('electron');
+
+exports.getPath = function () {
+  const dir = app.getAppPath();
+
+  return dir;
+}
+
+exports.openDir = function (dir = '') {
+  if (!dir) {
+    return false;
+  }
+  dir = getElectronPath(dir);
+  shell.openItem(dir);
+
+  return true;
+}
+
+function getElectronPath (filepath) {
+  //filepath = path.resolve(filepath);
+  filepath=filepath.replace("resources",""); 
+  filepath=filepath.replace("app.asar",""); 
+  filepath = path.normalize(filepath);
+  return filepath;
+};

+ 29 - 6
electron/config.js

@@ -5,16 +5,33 @@ const dayjs = require('dayjs');
 const storage = require('./storage');
 
 const config = {
+  developmentMode: {
+    default: 'vue',
+    mode: {
+      vue: {
+        hostname: 'localhost',
+        port: 8080
+      },
+      react: {
+        hostname: 'localhost',
+        port: 3000
+      },
+      ejs: {
+        hostname: 'localhost',
+        port: 7068 // The same as the egg port
+      }
+    }
+  },
   log: {
     file: {
-      fileName: path.normalize('./logs/electron-' + dayjs().format('YYYY-MM-DD') + '.log'),
+      fileName: path.normalize(storage.getStorageDir() + 'logs/electron-' + dayjs().format('YYYY-MM-DD') + '.log'),
       level: 'silly', // error, warn, info, verbose, debug, silly
       format: '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}',
       maxSize: '1048576' // 1048576 (1mb) by default.
     }
   },
   windowsOption: {
-    width: 800,
+    width: 980,
     height: 600,
     minWidth: 800,
     minHeight: 600,
@@ -30,11 +47,13 @@ const config = {
     title: 'electron-egg',
     env: 'prod',
     port: 7068,
-    hostname: '0.0.0.0',
+    hostname: 'localhost',
     workers: 1
   },
   autoUpdate: {
-    enable: false,
+    windows: false, // windows可以开启;macOs 需要签名验证
+    macOS: false,
+    Linux: false,
     options: {
       provider: 'generic', // or github, s3, bintray
       url: 'https://raw.githubusercontent.com/wallace5303/electron-egg/master/' // resource dir
@@ -42,8 +61,12 @@ const config = {
   }
 }
 
-exports.get = function (flag = '') {
+exports.get = function (flag = '', env = 'prod') {
   console.log('[config] [get] flag:', flag);
+  if (flag === 'developmentMode') {
+    return config.developmentMode;
+  }
+
   if (flag === 'log') {
     return config.log;
   }
@@ -58,7 +81,7 @@ exports.get = function (flag = '') {
   
   if (flag === 'egg') {
     const eggConfig = storage.getEggConfig();
-    if (eggConfig.port) {
+    if (env === 'prod' && eggConfig.port) {
       config.egg.port = eggConfig.port;
     }
     return config.egg;

+ 28 - 0
electron/lib/AutoLaunch.js

@@ -0,0 +1,28 @@
+const { app } = require('electron');
+const { LOGIN_SETTING_OPTIONS } = require('./Constant').AutoLaunch;
+
+class AutoLaunch {
+  enable () {
+    const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin;
+    if (enabled) {
+      return true;
+    }
+    app.setLoginItemSettings({
+      ...LOGIN_SETTING_OPTIONS,
+      openAtLogin: true
+    })
+    return true;
+  }
+  
+  disable () {
+    app.setLoginItemSettings({ openAtLogin: false })
+    return true;
+  }
+
+  isEnabled () {
+    const enabled = app.getLoginItemSettings(LOGIN_SETTING_OPTIONS).openAtLogin;
+    return enabled;
+  }
+}
+
+module.exports = AutoLaunch;

+ 10 - 0
electron/lib/Constant.js

@@ -0,0 +1,10 @@
+module.exports = {
+  AutoLaunch: {
+    LOGIN_SETTING_OPTIONS: {
+      // For Windows
+      args: [
+        '--opened-at-login=1'
+      ]
+    }
+  },
+};

+ 9 - 2
electron/setup.js

@@ -3,12 +3,19 @@
 global.ELog = require('electron-log');
 const storage = require('./storage');
 const config = require('./config');
-const autoUpdater = require('./autoUpdater');
+const is = require('electron-is');
+const api = require('./api');
 
 module.exports = () => {
   storage.setup();
   logger();
-  autoUpdater.setup();
+  const updateConfig = config.get('autoUpdate');
+  if ((is.windows() && updateConfig.windows) || (is.macOS() && updateConfig.macOS)
+    || (is.linux() && updateConfig.linux)) {
+    const autoUpdater = require('./autoUpdater');
+    autoUpdater.setup();
+  }
+  api.setup();
 }
 
 function logger () {

+ 32 - 11
electron/storage.js

@@ -6,22 +6,23 @@ const FileSync = require('lowdb/adapters/FileSync');
 const fs = require('fs');
 const getPort = require('get-port');
 const utils = require('../app/utils/utils');
-
-const storageDir = path.normalize('./storage/');
+const storageKey = require('../app/const/storageKey');
+const os = require('os');
+const pkg = require('../package.json');
+const storageDb = 'db.json';
 
 exports.setup = function () {
-  // const userDataDir = app.getPath('userData');
-  // const storageDir = path.normalize(path.join(userDataDir, 'storage'));
+  const storageDir = this.getStorageDir();
   if (!fs.existsSync(storageDir)) {
     utils.mkdir(storageDir);
     utils.chmodPath(storageDir, '777');
   }
-  const file = storageDir + 'db.json';
+  const file = storageDir + storageDb;
   const adapter = new FileSync(file);
   const db = lowdb(adapter);
-
-  if (!db.has('egg_config').value()) {
-    db.set('egg_config', {}).write();
+  const eggConfigKey = storageKey.EGG_CONFIG;
+  if (!db.has(eggConfigKey).value()) {
+    db.set(eggConfigKey, {}).write();
   }
 
   return true;
@@ -29,7 +30,8 @@ exports.setup = function () {
 
 exports.instance = function (file = null) {
   if (!file) {
-      file = path.normalize('./storage/db.json');
+    const storageDir = this.getStorageDir();
+    file = path.normalize(storageDir + storageDb);
   }
   const isExist = fs.existsSync(file);
   if (!isExist) {
@@ -43,8 +45,9 @@ exports.instance = function (file = null) {
 };
 
 exports.getEggConfig = function () {
+  const key = storageKey.EGG_CONFIG;
   const res = this.instance()
-  .get('egg_config')
+  .get(key)
   .value();
 
   return res;
@@ -55,11 +58,29 @@ exports.setDynamicPort = async function () {
   // console.log('setDynamicPort eggConfig:', eggConfig);
   // const dynamicPort = await getPort({port: eggConfig.port})
   const dynamicPort = await getPort();
+  const key = storageKey.EGG_CONFIG + '.port';
   const res = this.instance()
-    .set('egg_config.port', dynamicPort)
+    .set(key, dynamicPort)
     .write();
   
   return res;
 };
 
+exports.setIpcDynamicPort = async function () {
+  const key = storageKey.ELECTRON_IPC + '.port';
+  const dynamicPort = await getPort();
+  this.instance()
+    .set(key, dynamicPort)
+    .write();
+  
+  return dynamicPort;
+};
+
+exports.getStorageDir = function () {
+  const userHomeDir = os.userInfo().homedir;
+  const storageDir = path.normalize(userHomeDir + '/' + pkg.name + '/');
+
+  return storageDir;
+}
+
 exports = module.exports;

+ 38 - 0
electron/tray.js

@@ -0,0 +1,38 @@
+'use strict';
+
+const {app, Tray, Menu} = require('electron');
+const path = require('path');
+const pkg = require('../package.json');
+
+module.exports = () => {
+  MAIN_WINDOW.on('close', (event) => {
+    if (!CAN_QUIT) {
+      MAIN_WINDOW.hide();
+      MAIN_WINDOW.setSkipTaskbar(true);
+      event.preventDefault();
+    }
+  });
+  MAIN_WINDOW.show();
+  let trayMenuTemplate = [{
+    label: '退出',
+    click: function () {
+      MAIN_WINDOW.destroy();
+      app.quit()
+    }
+  }]
+  let iconPath = path.join(app.getAppPath(), '/app/public/images/tray_logo.png');
+  APP_TRAY = new Tray(iconPath);
+  const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
+  APP_TRAY.setToolTip(pkg.softName);
+  APP_TRAY.setContextMenu(contextMenu);
+  APP_TRAY.on('click', function(){
+    if (MAIN_WINDOW.isVisible()) {
+      MAIN_WINDOW.hide();
+      MAIN_WINDOW.setSkipTaskbar(false);
+    } else {
+      MAIN_WINDOW.show();
+      MAIN_WINDOW.setSkipTaskbar(true);
+    }
+  });
+  return APP_TRAY;
+}

+ 3 - 0
frontend/.env

@@ -0,0 +1,3 @@
+NODE_ENV=production
+VUE_APP_PREVIEW=false
+VUE_APP_API_BASE_URL=

+ 3 - 0
frontend/.env.development

@@ -0,0 +1,3 @@
+NODE_ENV=development
+VUE_APP_PREVIEW=true
+VUE_APP_API_BASE_URL=http://localhost:7068

+ 3 - 0
frontend/.env.preview

@@ -0,0 +1,3 @@
+NODE_ENV=production
+VUE_APP_PREVIEW=true
+VUE_APP_API_BASE_URL=http://localhost:7068

+ 23 - 0
frontend/.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 24 - 0
frontend/README.md

@@ -0,0 +1,24 @@
+# antd-demo
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
frontend/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

+ 50 - 0
frontend/package.json

@@ -0,0 +1,50 @@
+{
+  "name": "electron-egg",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "ant-design-vue": "^1.7.2",
+    "axios": "^0.21.1",
+    "core-js": "^3.6.5",
+    "store": "^2.0.12",
+    "vue": "^2.6.11",
+    "vue-quill-editor": "^3.0.6",
+    "vue-router": "^3.4.9",
+    "vuex": "^3.6.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
frontend/public/favicon.ico


+ 17 - 0
frontend/public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 47 - 0
frontend/src/App.vue

@@ -0,0 +1,47 @@
+<template>
+  <div id="app">
+    <Layout />
+  </div>
+</template>
+
+<script>
+// import HelloWorld from './components/HelloWorld.vue'
+// import { Button } from 'ant-design-vue'
+import Layout from './views/Layout'
+
+export default {
+  name: 'App',
+  components: {
+    Layout
+  },
+  data() {
+    return {
+      current: ['mail'],
+      openKeys: ['sub1']
+    };
+  },
+  watch: {
+    openKeys(val) {
+      console.log('openKeys', val);
+    },
+  },
+  methods: {
+    handleClick(e) {
+      console.log('click', e);
+    },
+    titleClick(e) {
+      console.log('titleClick', e);
+    },
+  }
+}
+</script>
+
+<style>
+#app {
+  font-family: Avenir, Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  text-align: center;
+  color: #2c3e50;
+}
+</style>

+ 55 - 0
frontend/src/api/main.js

@@ -0,0 +1,55 @@
+import request from '@/utils/request'
+// import storage from 'store'
+
+const mainApi = {
+  outApi: '/api/v1/outApi',
+  openDir: '/api/v1/example/openLocalDir',
+  uploadFile: '/api/v1/example/uploadFile',
+  autoLaunchEnable: '/api/v1/setting/autoLaunchEnable',
+  autoLaunchDisable: '/api/v1/setting/autoLaunchDisable',
+  autoLaunchIsEnabled: '/api/v1/setting/autoLaunchIsEnabled'
+}
+
+/**
+ * outApi
+ */
+export function outApi (uri, parameter) {
+  return request({
+    url: mainApi[uri],
+    method: 'post',
+    data: parameter
+  })
+}
+
+/**
+ * local api
+ */
+export function localApi (uri, parameter) {
+  return request({
+    url: mainApi[uri],
+    method: 'post',
+    data: parameter
+  })
+}
+
+/**
+ * openDir
+ */
+export function openDir (parameter) {
+  return request({
+    url: mainApi.openDir,
+    method: 'post',
+    data: parameter
+  })
+}
+
+/**
+ * uploadFile
+ */
+export function uploadFile (parameter) {
+  return request({
+    url: mainApi.uploadFile,
+    method: 'post',
+    data: parameter
+  })
+}

BIN
frontend/src/assets/logo.png


+ 58 - 0
frontend/src/components/HelloWorld.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br>
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
+      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
+      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
+      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
+      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
+      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
+      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
+      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
+      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  props: {
+    msg: String
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 27 - 0
frontend/src/config/router.config.js

@@ -0,0 +1,27 @@
+/**
+ * 基础路由
+ * @type { *[] }
+ */
+export const constantRouterMap = [
+  {
+    path: '/',
+    component: { template: '<div><router-view /></div>' },
+    children: [
+      {
+        path: 'fileOpenDir',
+        name: 'FileOpenDir',
+        component: () => import('@/views/file/OpenDir')
+      },
+      {
+        path: 'uploadFile',
+        name: 'UploadFile',
+        component: () => import('@/views/file/UploadFile')
+      },
+      {
+        path: 'setting',
+        name: 'Setting',
+        component: () => import('@/views/Setting')
+      }
+    ]
+  }
+]

+ 17 - 0
frontend/src/main.js

@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import antd from 'ant-design-vue';
+import 'ant-design-vue/dist/antd.css';
+import App from './App';
+import router from './router';
+import { VueAxios } from './utils/request'
+
+Vue.use(antd);
+// mount axios to `Vue.$http` and `this.$http`
+Vue.use(VueAxios)
+
+Vue.config.productionTip = false;
+
+new Vue({
+  router,
+  render: h => h(App),
+}).$mount('#app');

+ 17 - 0
frontend/src/router/index.js

@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import { constantRouterMap } from '@/config/router.config'
+
+// hack router push callback
+const originalPush = Router.prototype.push
+Router.prototype.push = function push (location, onResolve, onReject) {
+  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
+  return originalPush.call(this, location).catch(err => err)
+}
+
+Vue.use(Router)
+
+export default new Router({
+  mode: 'history',
+  routes: constantRouterMap
+})

+ 35 - 0
frontend/src/utils/axios.js

@@ -0,0 +1,35 @@
+const VueAxios = {
+  vm: {},
+  // eslint-disable-next-line no-unused-vars
+  install (Vue, instance) {
+    if (this.installed) {
+      return
+    }
+    this.installed = true
+
+    if (!instance) {
+      // eslint-disable-next-line no-console
+      console.error('You have to install axios')
+      return
+    }
+
+    Vue.axios = instance
+
+    Object.defineProperties(Vue.prototype, {
+      axios: {
+        get: function get () {
+          return instance
+        }
+      },
+      $http: {
+        get: function get () {
+          return instance
+        }
+      }
+    })
+  }
+}
+
+export {
+  VueAxios
+}

+ 61 - 0
frontend/src/utils/request.js

@@ -0,0 +1,61 @@
+import axios from 'axios'
+import storage from 'store'
+import notification from 'ant-design-vue/es/notification'
+import { VueAxios } from './axios'
+
+// 创建 axios 实例
+const request = axios.create({
+  // API 请求的默认前缀
+  baseURL: process.env.VUE_APP_API_BASE_URL,
+  timeout: 6000 // 请求超时时间
+})
+
+// 异常拦截处理器
+const errorHandler = (error) => {
+  if (error.response) {
+    const data = error.response.data
+    if (error.response.status === 403) {
+      notification.error({
+        message: 'Forbidden',
+        description: data.message
+      })
+    }
+    if (error.response.status === 401 && !(data.result && data.result.isLogin)) {
+      notification.error({
+        message: 'Unauthorized',
+        description: 'Authorization verification failed'
+      })
+    }
+  }
+  return Promise.reject(error)
+}
+
+// request interceptor
+request.interceptors.request.use(config => {
+  const token = storage.get('token')
+  // 如果 token 存在
+  // 让每个请求携带自定义 token 请根据实际情况自行修改
+  if (token) {
+    config.headers['Access-Token'] = token
+  }
+  return config
+}, errorHandler)
+
+// response interceptor
+request.interceptors.response.use((response) => {
+  return response.data
+}, errorHandler)
+
+const installer = {
+  vm: {},
+  install (Vue) {
+    Vue.use(VueAxios, request)
+  }
+}
+
+export default request
+
+export {
+  installer as VueAxios,
+  request as axios
+}

+ 68 - 0
frontend/src/utils/util.js

@@ -0,0 +1,68 @@
+export function timeFix () {
+  const time = new Date()
+  const hour = time.getHours()
+  return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好'
+}
+
+export function welcome () {
+  const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了']
+  const index = Math.floor(Math.random() * arr.length)
+  return arr[index]
+}
+
+/**
+ * 触发 window.resize
+ */
+export function triggerWindowResizeEvent () {
+  const event = document.createEvent('HTMLEvents')
+  event.initEvent('resize', true, true)
+  event.eventType = 'message'
+  window.dispatchEvent(event)
+}
+
+export function handleScrollHeader (callback) {
+  let timer = 0
+
+  let beforeScrollTop = window.pageYOffset
+  callback = callback || function () {}
+  window.addEventListener(
+    'scroll',
+    event => {
+      clearTimeout(timer)
+      timer = setTimeout(() => {
+        let direction = 'up'
+        const afterScrollTop = window.pageYOffset
+        const delta = afterScrollTop - beforeScrollTop
+        if (delta === 0) {
+          return false
+        }
+        direction = delta > 0 ? 'down' : 'up'
+        callback(direction)
+        beforeScrollTop = afterScrollTop
+      }, 50)
+    },
+    false
+  )
+}
+
+export function isIE () {
+  const bw = window.navigator.userAgent
+  const compare = (s) => bw.indexOf(s) >= 0
+  const ie11 = (() => 'ActiveXObject' in window)()
+  return compare('MSIE') || ie11
+}
+
+/**
+ * Remove loading animate
+ * @param id parent element id or class
+ * @param timeout
+ */
+export function removeLoadingAnimate (id = '', timeout = 1500) {
+  if (id === '') {
+    return
+  }
+  setTimeout(() => {
+    document.body.removeChild(document.getElementById(id))
+  }, timeout)
+}
+  

+ 169 - 0
frontend/src/views/Layout.vue

@@ -0,0 +1,169 @@
+<template>
+  <a-layout id="components-layout-demo-responsive">
+    <a-layout-sider
+      v-model="collapsed"
+      theme="light"
+      class="layout-sider"
+    >
+      <div class="logo"><img class="pic-logo" src="~@/assets/logo.png"></div>
+      <a-menu class="menu-item" theme="light" mode="inline" @click="menuHandle" :default-selected-keys="['menu_1']">
+        <a-menu-item :key="index" v-for="(menuInfo, index) in menu" :title="menuInfo.title">
+          <a-icon :type="menuInfo.icon" />
+        </a-menu-item>
+      </a-menu>
+    </a-layout-sider>
+    <a-layout>
+      <a-layout-sider
+        theme="light"
+        class="sub-layout-sider"
+      >
+        <a-menu class="sub-menu-item" theme="light" mode="inline" v-model="subMenuKey" :default-selected-keys="subMenuKey">
+          <a-menu-item :key="subIndex" v-for="(menuInfo, subIndex) in subMenu">
+          <router-link :to="{ name: menuInfo.pageName, params: menuInfo.params}">
+            <span>{{ menuInfo.title }}</span>
+          </router-link>
+          </a-menu-item>
+        </a-menu>
+      </a-layout-sider>
+      <a-layout-content :style="{}">
+        <div :style="{ padding: '10px', background: '#fff', minHeight: '560px' }">
+          <router-view />
+        </div>
+      </a-layout-content>
+    </a-layout>
+  </a-layout>
+</template>
+<script>
+export default {
+  name: 'Layout',
+  data() {
+    return {
+      collapsed: true,
+      menu: {
+        'menu_1' : {
+          icon: 'home',
+          title: ''
+        },
+        'menu_2' : {
+          icon: 'setting',
+          title: ''
+        },
+      },
+      menuKey: 'menu_1',
+      subMenuKey: ['subMenu_1'],
+      subMenu: {},
+      subMenuList: {
+        'menu_1' : {
+          'subMenu_1' : {
+            title: '上传文件到sm图床',
+            pageName: 'UploadFile',
+            params: {}
+          },
+          'subMenu_2' : {
+            title: '打开文件夹',
+            pageName: 'FileOpenDir',
+            params: {},
+          }
+        },
+        'menu_2' : {
+          'subMenu_1' : {
+            title: '基础设置',
+            pageName: 'Setting',
+            params: {},
+          }
+        },
+      },
+      contentPage: ''
+    };
+  },
+  mounted () {
+    this.menuHandle({key: 'menu_1'})
+  },
+  methods: {
+    menuHandle (item) {
+      this.subMenu = this.subMenuList[item.key]
+      this.subMenuKey = ['subMenu_1']
+      const linkInfo = this.subMenu['subMenu_1']
+      this.$router.push({ name: linkInfo.pageName, params: linkInfo.params})
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+// 嵌套
+#components-layout-demo-responsive {
+  .logo {
+    border-bottom: 1px solid #e8e8e8;
+  }
+  .pic-logo {
+    height: 32px;
+    //background: rgba(139, 137, 137, 0.2);
+    margin: 10px;
+  }
+  .layout-sider {
+    border-right: 1px solid #e8e8e8;
+  }
+  .menu-item {
+    .ant-menu-item {
+      background-color: #fff;
+      margin-top: 0px;
+      margin-bottom: 0px;
+    }
+  }
+  .sub-layout-sider {
+    background-color: #FAFAFA;
+  }
+  .sub-menu-item {
+    .ant-menu-item {
+      margin-top: 0px;
+      margin-bottom: 0px;
+    }
+    .ant-menu-item::after {
+      border-right: 3px solid #F2F2F2;
+    }
+    .ant-menu-item-selected {
+      background-color:#F2F2F2;
+      span {
+        color: #111;
+      }
+    }
+  }
+  .sub-menu-item.ant-menu {
+    background: #FAFAFA;
+  }
+  .sub-menu-item.ant-menu-inline {
+    border-right: 0px solid #FAFAFA;
+  }
+}
+
+// #components-layout-demo-responsive .logo {
+//   height: 32px;
+//   background: rgba(139, 137, 137, 0.2);
+//   margin: 16px;
+// }
+// #components-layout-demo-responsive .menu-item .ant-menu-item {
+//   background-color: #001529;
+//   margin-top: 0px;
+//   margin-bottom: 0px;
+// }
+// #components-layout-demo-responsive .sub-menu-item .ant-menu-item {
+//   margin-top: 0px;
+//   margin-bottom: 0px;
+// }
+// #components-layout-demo-responsive .sub-menu-item .ant-menu-item::after {
+//   border-right: 3px solid #F2F2F2;
+// }
+// #components-layout-demo-responsive .sub-menu-item.ant-menu {
+//   background: #FAFAFA;
+// }
+// #components-layout-demo-responsive .sub-menu-item.ant-menu-inline {
+//   border-right: 0px solid #FAFAFA;
+// }
+// #components-layout-demo-responsive .sub-menu-item .ant-menu-item-selected {
+//   background-color:#F2F2F2;
+//   span {
+//     color: #111;
+//   }
+// }
+</style>

+ 77 - 0
frontend/src/views/Setting.vue

@@ -0,0 +1,77 @@
+<template>
+  <a-list id="set" itemLayout="horizontal">
+    <a-list-item style="text-align: left;">
+      <a-list-item-meta>
+        <template v-slot:title>
+          <a>启动</a>
+        </template>
+        <template v-slot:description>
+          <span>
+            开机自动启动
+          </span>
+        </template>
+      </a-list-item-meta>
+      <template v-slot:actions>
+        <a-switch checkedChildren="开" unCheckedChildren="关" v-model="autoLaunchChecked" @change="autoLaunchChange(autoLaunchChecked)" />
+      </template>
+    </a-list-item>
+  </a-list>
+</template>
+<script>
+import { localApi } from '@/api/main'
+
+export default {
+  data () {
+    return {
+      autoLaunchChecked: false
+    }
+  },
+  mounted () {
+    this.autoLaunchInit()
+  },
+  methods: {
+    autoLaunchInit () {
+      localApi('autoLaunchIsEnabled', {}).then(res => {
+        if (res.code !== 0) {
+          return false
+        }
+        this.autoLaunchChecked = res.data.isEnabled;
+      }).catch(err => {
+        console.log('err:', err)
+      })
+    },
+    autoLaunchChange (checkStatus) {
+      const params = {
+        'checkStatus': checkStatus
+      }
+      if (checkStatus) {
+        localApi('autoLaunchEnable', params).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+          this.autoLaunchChecked = res.data.isEnabled;
+        }).catch(err => {
+          console.log('err:', err)
+        })
+      } else {
+        localApi('autoLaunchDisable', params).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+          this.autoLaunchChecked = res.data.isEnabled;
+        }).catch(err => {
+          console.log('err:', err)
+        })        
+      }
+    },
+  }
+}
+</script>
+<style lang="less" scoped>
+// 嵌套
+#set {
+  .ant-list-item:last-child {
+    border-bottom: 1px solid #e8e8e8;
+  }
+}  
+</style>  

+ 62 - 0
frontend/src/views/file/OpenDir.vue

@@ -0,0 +1,62 @@
+<template>
+  <div>
+    <h3 :style="{ marginBottom: '16px' }">
+      demo1 打开文件夹实现
+    </h3>
+    <a-list bordered :data-source="data">
+      <a-list-item @click="openDirectry(item.id)" slot="renderItem" slot-scope="item">
+        {{ item.content }}
+        <a-button type="link">
+          打开
+        </a-button>
+      </a-list-item>
+    </a-list>
+  </div>
+</template>
+<script>
+import { openDir } from '@/api/main'
+
+const data = [
+  {
+    content: '【下载】目录',
+    id: 'download'
+  },
+  {
+    content: '【图片】目录',
+    id: 'picture'
+  },
+  {
+    content: '【文档】目录',
+    id: 'doc'
+  },
+  {
+    content: '【音乐】目录',
+    id: 'music'
+  }
+];
+
+export default {
+  data() {
+    return {
+      data,
+    };
+  },
+  methods: {
+    openDirectry (id) {
+      console.log('id:', id)
+      const params = {
+        'id': id
+      }
+      openDir(params).then(res => {
+        if (res.code !== 0) {
+          return false
+        }
+
+        }).catch(err => {
+          console.log('err:', err)
+        })
+    },
+  }
+};
+</script>
+<style></style>

+ 80 - 0
frontend/src/views/file/UploadFile.vue

@@ -0,0 +1,80 @@
+<template>
+  <div>
+    <h3 :style="{ marginBottom: '16px' }">
+      demo2 上传文件到sm图床实现
+    </h3>
+    <!-- dev调试时,action参数:请填写你本地完整的api地址,如:http://localhost:7068/api/v1/example/uploadFile -->
+    <a-upload-dragger
+        name="file"
+        :multiple="true"
+        :action="action_url"
+        @change="handleChange"
+    >
+        <p class="ant-upload-drag-icon">
+        <a-icon type="inbox" />
+        </p>
+        <p class="ant-upload-text">
+        Click or drag file to this area to upload
+        </p>
+        <p class="ant-upload-hint">
+        Support for a single or bulk upload. Strictly prohibit from uploading company data or other
+        band files
+        </p>
+    </a-upload-dragger>
+    <!-- <a-card hoverable style="width: 240px">
+      <img
+        slot="cover"
+        alt="example"
+        src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png"
+      />
+
+    </a-card> -->
+    <p/>
+    <a-list v-if="image_info.length !== 0" size="small" bordered :data-source="image_info">
+      <a-list-item style="text-align:left;" slot="renderItem" slot-scope="item">
+        {{ item.id }}.&nbsp;{{ item.imageUrlText }}:&nbsp;
+        <a :href="item.url" target="_blank">{{ item.url }}</a>
+      </a-list-item>
+    </a-list>
+  </div>
+</template>
+<script>
+//import { uploadFile } from '@/api/main'
+
+export default {
+  data() {
+    return {
+      action_url: process.env.VUE_APP_API_BASE_URL + '/api/v1/example/uploadFile',
+      image_info: [],
+      num: 0
+    };
+  },
+  methods: {
+    handleChange(info) {
+      const status = info.file.status;
+      if (status !== 'uploading') {
+        console.log(info.file);
+      }
+      if (status === 'done') {
+        // 去除list列表
+        //info.fileList = [];
+        const uploadRes = info.file.response;
+        console.log('uploadRes:', uploadRes)
+        if (uploadRes.code !== 'success') {
+          this.$message.error(`file upload failed ${uploadRes.code} .`);
+          return false;
+        }
+        this.num++;
+        const picInfo = uploadRes.data;
+        picInfo.id = this.num;
+        picInfo.imageUrlText = 'image url';
+        this.image_info.push(picInfo);
+        this.$message.success(`${info.file.name} file uploaded successfully.`);
+      } else if (status === 'error') {
+        this.$message.error(`${info.file.name} file upload failed.`);
+      }
+    }
+  }
+};
+</script>
+<style></style>

+ 11 - 0
frontend/vue.config.js

@@ -0,0 +1,11 @@
+module.exports = {
+    //Solution For Issue:You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
+    //zhengkai.blog.csdn.net
+    runtimeCompiler: true,
+    configureWebpack: (config) => {
+      config["performance"] = {
+        "maxEntrypointSize": 10000000,
+        "maxAssetSize": 30000000,
+      }
+    }
+  }

+ 60 - 32
main.js

@@ -1,33 +1,35 @@
-const {app, BrowserWindow, Menu, shell} = require('electron')
+const {app, BrowserWindow, Menu} = require('electron')
 const path = require('path')
 const eggLauncher = require('./electron/lanucher')
 const setup = require('./electron/setup')
 const electronConfig = require('./electron/config')
 const storage = require('./electron/storage')
-const autoUpdater = require('./electron/autoUpdater')
+const is = require('electron-is')
+const setTray = require('./electron/tray')
 
 // main window
 global.MAIN_WINDOW = null
+global.APP_TRAY = null;
+global.CAN_QUIT = false;
 
 // Initialize 
 setup()
-// return
+//return
 
-if (process.mas) app.setName('electron-egg')
+// argv
+let ENV = 'prod'
+for (let i = 0; i < process.argv.length; i++) {
+  const tmpArgv = process.argv[i]
+  if (tmpArgv.indexOf('--env=') !== -1) {
+    ENV = tmpArgv.substr(6)
+  }
+}
+const eggConfig = electronConfig.get('egg', ENV)
+eggConfig.env = ENV
 
-// Open url with the default browser
-app.on('web-contents-created', (e, webContents) => {
-    webContents.on('new-window', (event, url) => {
-        event.preventDefault()
-        shell.openExternal(url)
-    });
-});
+if (process.mas) app.setName('electron-egg')
 
 async function initialize () {
-
-  // dynamic port
-  await storage.setDynamicPort();
-  
   app.whenReady().then(() => {
     createWindow()
     app.on('activate', function () {
@@ -43,18 +45,17 @@ async function initialize () {
       app.quit()
     }
   })
+
+  // Open url with the default browser
+  // app.on('web-contents-created', (e, webContents) => {
+  //   webContents.on('new-window', (event, url) => {
+  //       event.preventDefault()
+  //       shell.openExternal(url)
+  //   });
+  // });
 }
 
 async function createWindow () {
-  // argv
-  const eggConfig = electronConfig.get('egg')
-  for (let i = 0; i < process.argv.length; i++) {
-    const tmpArgv = process.argv[i]
-    if (tmpArgv.indexOf('--env=') !== -1) {
-      eggConfig.env = tmpArgv.substr(6)
-    }
-  }
-
   MAIN_WINDOW = new BrowserWindow(electronConfig.get('windowsOption'))
   
   // if (process.platform === 'linux') {
@@ -64,36 +65,63 @@ async function createWindow () {
   if (eggConfig.env === 'prod') {
     // hidden menu
     Menu.setApplicationMenu(null)
+
+    // dynamic port
+    await storage.setDynamicPort()
+    eggConfig.port = electronConfig.get('egg', eggConfig.env).port
   }
 
   // loding page
   MAIN_WINDOW.loadURL(path.join('file://', __dirname, '/app/public/loading.html'))
   
+  // tray 
+  setTray();
+
   // egg server
-  setTimeout(function(){
-    startServer(eggConfig)
-  }, 100)
+  await startServer(eggConfig)
 
   // check update
   const updateConfig = electronConfig.get('autoUpdate')
-  if (updateConfig.enable) {
-    autoUpdater.checkUpdate()
+  if ((is.windows() && updateConfig.windows) || (is.macOS() && updateConfig.macOS)
+    || (is.linux() && updateConfig.linux)) {
+    const autoUpdater = require('./electron/autoUpdater');
+    autoUpdater.checkUpdate();
   }
 
   return MAIN_WINDOW
 }
 
 async function startServer (options) {
-  let startRes = null
   ELog.info('[main] [startServer] options', options)
+  const protocol = 'http://'
+  let startRes = null
+  let url = null
+  if (ENV === 'prod') {
+    url = protocol + options.hostname + ':' + options.port
+  } else {
+    const developmentModeConfig = electronConfig.get('developmentMode', ENV)
+    const selectMode = developmentModeConfig.default
+    const modeInfo = developmentModeConfig.mode[selectMode]
+    switch (selectMode) {
+      case 'vue' :
+        url = protocol + modeInfo.hostname + ':' + modeInfo.port
+        break
+      case 'react' :
+        url = protocol + modeInfo.hostname + ':' + modeInfo.port
+        break
+      case 'ejs' :
+        url = protocol + modeInfo.hostname + ':' + modeInfo.port
+        break
+    }
+  }
+  ELog.info('[main] [url]:', url)
   startRes = await eggLauncher.start(options).then((res) => res, (err) => err)
   ELog.info('[main] [startServer] startRes:', startRes)
   if (startRes === 'success') {
-    let url = 'http://localhost:' + options.port
     MAIN_WINDOW.loadURL(url)
-
     return true
   }
+  
   app.relaunch()
 }
 

+ 12 - 7
package.json

@@ -1,8 +1,9 @@
 {
   "name": "electron-egg",
-  "version": "1.2.0",
+  "version": "1.8.0",
   "description": "A fast, desktop software development framework",
   "main": "main.js",
+  "softName": "electron-egg",
   "scripts": {
     "start": "electron .",
     "dev": "electron . --env=local",
@@ -11,7 +12,8 @@
     "build-l": "electron-builder -l",
     "web-start": "egg-scripts start --daemon --title=electron-egg --ignore-stderr --env=prod --workers=1",
     "web-stop": "egg-scripts stop --title=electron-egg",
-    "web-dev": "egg-bin dev"
+    "web-dev": "egg-bin dev",
+    "rd": "node ./tools/replace_dist --dist_dir=./frontend/dist"
   },
   "build": {
     "productName": "electron-egg",
@@ -86,17 +88,18 @@
   "author": "wallace5303",
   "license": "Apache",
   "devDependencies": {
-    "devtron": "^1.4.0",
-    "electron": "^8.4.1",
-    "electron-builder": "^22.7.0",
     "autod": "^3.0.1",
     "autod-egg": "^1.1.0",
+    "devtron": "^1.4.0",
     "egg-bin": "^4.12.3",
     "egg-ci": "^1.11.0",
     "egg-mock": "^3.21.0",
+    "electron": "^8.4.1",
+    "electron-builder": "^22.7.0",
     "eslint": "^5.13.0",
     "eslint-config-egg": "^7.1.0",
     "eslint-plugin-prettier": "^3.0.1",
+    "fs-extra": "^9.1.0",
     "prettier": "^1.16.4",
     "webstorm-disable-index": "^1.2.0"
   },
@@ -107,12 +110,14 @@
     "egg-jwt": "^3.1.6",
     "egg-scripts": "^2.13.0",
     "egg-view-ejs": "^2.0.0",
+    "electron-is": "^3.0.0",
     "electron-log": "^4.2.2",
-    "electron-updater": "^4.3.5",
     "get-port": "^5.1.1",
     "glob": "^7.1.6",
     "lodash": "^4.17.11",
     "lowdb": "^1.0.0",
-    "semver": "^5.4.1"
+    "semver": "^5.4.1",
+    "socket.io": "^3.0.5",
+    "socket.io-client": "^3.0.5"
   }
 }

+ 0 - 2
storage/.gitignore

@@ -1,2 +0,0 @@
-*
-!.gitignore

+ 30 - 0
tools/replace_dist.js

@@ -0,0 +1,30 @@
+'use strict';
+const path = require('path');
+const fs = require('fs');
+const fsPro = require('fs-extra');
+
+console.log('moving frontend asset to egg public dir');
+
+// argv
+let distDir = '';
+for (let i = 0; i < process.argv.length; i++) {
+  const tmpArgv = process.argv[i]
+  if (tmpArgv.indexOf('--dist_dir=') !== -1) {
+    distDir = tmpArgv.substr(11)
+  }
+}
+
+const sourceDir = path.normalize(distDir);
+distDir = path.normalize('./app/public');
+
+// del dir and move
+fs.rmdirSync(distDir, {recursive: true});
+fsPro.copySync(sourceDir, distDir);
+
+// replace ejs
+const sourceFile = path.normalize(distDir + '/index.html');
+const distFile = path.normalize( './app/view/index.ejs');
+fsPro.copySync(sourceFile, distFile);
+
+console.log('Move over');
+

Some files were not shown because too many files changed in this diff