浏览代码

样式重构 && 系统弹窗 && ipc通信 等等

gaoshuaixing 4 年之前
父节点
当前提交
de14bea472

+ 0 - 22
app/controller/test.js

@@ -1,22 +0,0 @@
-'use strict';
-
-const BaseController = require('./base');
-
-class TestController extends BaseController {
-  async index() {
-    const { app, ctx, service } = this;
-    const query = ctx.request.query;
-    console.log('env:%j', app.config.env);
-    const res = 0;
-    const data = {
-      env: app.config.env,
-    };
-
-
-
-    console.log('res:%j', res);
-    this.sendSuccess(data, 'ok');
-  }
-}
-
-module.exports = TestController;

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

@@ -7,6 +7,20 @@ const path = require('path');
 
 class ExampleController extends BaseController {
 
+  /**
+   * test electron api
+   */
+  async testElectronApi() {
+    const { ctx, service } = this;
+    const body = ctx.request.body;
+    const id = body.id;
+    const data = {};
+
+    await service.example.testElectronApi(id);
+
+    this.sendSuccess(data);
+  }
+
   async openLocalDir() {
     const self = this;
     const { ctx, service } = this;
@@ -211,6 +225,28 @@ class ExampleController extends BaseController {
 
     this.sendSuccess(data);
   }
+
+  /**
+   * 显示消息对话框
+   */
+  async messageShow() {
+    const { service } = this;
+    const data = {};
+    await service.example.messageShow();
+
+    this.sendSuccess(data);
+  }
+
+  /**
+   * 显示消息对话框和确认
+   */
+  async messageShowConfirm() {
+    const { service } = this;
+    const data = {};
+    await service.example.messageShowConfirm();
+
+    this.sendSuccess(data);
+  }
 }
 
 module.exports = ExampleController;

+ 7 - 0
app/router/example.js

@@ -33,4 +33,11 @@ module.exports = app => {
   router.post('/api/v1/example/openSoftware', controller.v1.example.openSoftware);
   // select file dir
   router.post('/api/v1/example/selectFileDir', controller.v1.example.selectFileDir);
+  // test some electron api
+  router.post('/api/v1/example/testElectronApi', controller.v1.example.testElectronApi);
+  // message show
+  router.post('/api/v1/example/messageShow', controller.v1.example.messageShow);
+  // message show confirm
+  router.post('/api/v1/example/messageShowConfirm', controller.v1.example.messageShowConfirm);
+
 };

+ 20 - 0
app/service/example.js

@@ -91,7 +91,27 @@ class ExampleService extends BaseService {
     }
 
     return result.data;
+  }
+
+  async testElectronApi(id = 0) {
+    await this.ipcCall('example.testElectronApi');
+
+    return null;
+  }
+
+  async messageShow() {
+    await this.ipcCall('example.messageShow');
+
+    return true;
   } 
+
+  async messageShowConfirm() {
+    await this.ipcCall('example.messageShowConfirm');
+
+    return true;
+  }   
+
+
 }
 
 module.exports = ExampleService;

+ 0 - 7
app/service/test.js

@@ -1,7 +0,0 @@
-'use strict';
-
-const BaseService = require('./base');
-
-class TestService extends BaseService {}
-
-module.exports = TestService;

+ 50 - 3
electron/apis/example.js

@@ -1,5 +1,9 @@
 'use strict';
 
+/**
+ * egg服务调用electron功能时,建议使用该模块
+ */
+
 const path = require('path');
 const fs = require('fs');
 const _ = require('lodash');
@@ -7,7 +11,6 @@ const {exec} = require('child_process');
 const {app, webContents, shell, dialog} = require('electron');
 const AutoLaunchManager = require('../lib/autoLaunch');
 const shortcut = require('../lib/shortcut');
-const eLogger = require('../lib/eLogger').get();
 
 /**
  * app根目录
@@ -107,8 +110,8 @@ exports.openSoftware = function (softName = '') {
 /**
  * 选择目录
  */
- exports.selectDir = function () {
-  var filePaths = dialog.showOpenDialogSync({
+exports.selectDir = function () {
+  const filePaths = dialog.showOpenDialogSync({
     properties: ['openDirectory', 'createDirectory']
   });
   console.log('[example] [selectDir] filePaths:', filePaths);
@@ -119,6 +122,50 @@ exports.openSoftware = function (softName = '') {
   return filePaths[0];
 }
 
+/**
+ * 测试用的 - 忽略
+ */
+exports.testElectronApi = function () {
+  const filePaths = dialog.showSaveDialogSync({
+    properties: ['openFile', 'multiSelections']
+  });
+  console.log('[example] [testElectronApi] filePaths:', filePaths);
+
+  return true;
+}
+
+/**
+ * 显示消息对话框
+ */
+exports.messageShow = function () {
+  dialog.showMessageBoxSync({
+    type: 'info', // "none", "info", "error", "question" 或者 "warning"
+    title: '自定义标题-message',
+    message: '自定义消息内容',
+    detail: '其它的额外信息'
+  })
+
+  return true;
+}
+
+/**
+ * 显示消息对话框和确认
+ */
+exports.messageShowConfirm = function () {
+  const res = dialog.showMessageBoxSync({
+    type: 'info',
+    title: '自定义标题-message',
+    message: '自定义消息内容',
+    detail: '其它的额外信息',
+    cancelId: 1, // 用于取消对话框的按钮的索引
+    defaultId: 0, // 设置默认选中的按钮
+    buttons: ['确认', '取消'], // 按钮及索引
+  })
+  console.log('[example] [messageShowConfirm] 结果:', res, res === 0 ? '点击确认按钮' : '点击取消按钮');
+
+  return true;
+}
+
 function getElectronPath(filepath) {
   //filepath = path.resolve(filepath);
   filepath = filepath.replace("resources", "");

+ 66 - 3
electron/ipc/example.js

@@ -1,8 +1,71 @@
-const { answerRenderer } = require('./index')
+'use strict';
 
-answerRenderer('example.hello', async (msg) => {
+/**
+ * 前端(html)调用electron功能时,建议使用该模块
+ * 
+ * 定义的function 接收三个参数 
+ * @param event ipcMain事件对象
+ * @param channel 频道
+ * @param arg 接收到的消息
+ */
+
+const {dialog} = require('electron');
+
+let myTimer = null;
+
+exports.hello = function (event, channel, msg) {
   let newMsg = msg + " +1"
   let reply = ''
   reply = '收到:' + msg + ',返回:' + newMsg
   return reply
-})
+}
+
+exports.messageShow = function (event, channel, arg) {
+  dialog.showMessageBoxSync({
+    type: 'info', // "none", "info", "error", "question" 或者 "warning"
+    title: '自定义标题-message',
+    message: '自定义消息内容',
+    detail: '其它的额外信息'
+  })
+
+  return '打开了消息框';
+}
+
+exports.messageShowConfirm = function (event, channel, arg) {
+  const res = dialog.showMessageBoxSync({
+    type: 'info',
+    title: '自定义标题-message',
+    message: '自定义消息内容',
+    detail: '其它的额外信息',
+    cancelId: 1, // 用于取消对话框的按钮的索引
+    defaultId: 0, // 设置默认选中的按钮
+    buttons: ['确认', '取消'], // 按钮及索引
+  })
+  let data = (res === 0) ? '点击确认按钮' : '点击取消按钮';
+  console.log('[electron] [example] [messageShowConfirm] 结果:', res, );
+
+  return data;
+}
+
+/**
+ * 长消息 - 开始
+ */
+exports.socketMessageStart = function (event, channel, arg) {
+  // 每隔1秒,向前端页面发送消息
+  // 用定时器模拟
+  myTimer = setInterval(function(e, c, msg) {
+    let timeNow = Date.now();
+    let data = msg + ':' + timeNow;
+    e.reply(`${c}`, data)
+  }, 1000, event, channel, arg)
+
+  return '开始了'
+}
+
+/**
+ * 长消息 - 停止
+ */
+exports.socketMessageStop = function () {
+  clearInterval(myTimer);
+  return '停止了'
+}

+ 0 - 40
electron/ipc/index.js

@@ -1,40 +0,0 @@
-const { ipcMain: ipc } = require('electron')
-const path = require('path')
-const fs = require('fs')
-
-/**
- * 发送响应信息给渲染进程
- * @param event
- * @param channel
- * @param data
- * @private
- */
-const _echo = (event, channel, data) => {
-  event.reply(`${channel}`, data)
-}
-
-/**
- * 执行主进程函数,并响应渲染进程
- * @param channel
- * @param callback
- */
-module.exports.answerRenderer = (channel, callback) => {
-  ipc.on(channel, async (event, param) => {
-    const result = await callback(param)
-    _echo(event, channel, result)
-  })
-}
-
-/**
- * 加载所有的主程序
- */
-module.exports.setup = () => {
-  const ipcDir = path.normalize(__dirname + '/')
-
-  fs.readdirSync(ipcDir).forEach(function (filename) {
-    if (path.extname(filename) === '.js' && filename !== 'index.js') {
-      const filePath = path.join(ipcDir, filename)
-      require(filePath)
-    }
-  })
-}

+ 0 - 2
electron/lib/api.js

@@ -7,7 +7,6 @@ const _ = require('lodash');
 const storage = require('./storage');
 const socketIo = require('socket.io');
 const eLogger = require('./eLogger').get();
-// const {app} = require('electron');
 
 const apis = {};
 
@@ -112,5 +111,4 @@ function setApi() {
  */
 function getApiName (jsname, method) {
   return jsname + '.' + method;
-  //return jsname + method.charAt(0).toUpperCase() + method.slice(1);
 }

+ 68 - 0
electron/lib/ipcMain.js

@@ -0,0 +1,68 @@
+const { ipcMain: ipc } = require('electron')
+const path = require('path')
+const fs = require('fs')
+const _ = require('lodash');
+
+/**
+ * 发送响应信息给渲染进程
+ * @param event
+ * @param channel
+ * @param data
+ * @private
+ */
+const _echo = (event, channel, data) => {
+  console.log('[ipc] [answerRenderer] result: ', {channel, data})
+  event.reply(`${channel}`, data)
+}
+
+/**
+ * 执行主进程函数,并响应渲染进程
+ * @param channel
+ * @param callback
+ */
+const answerRenderer = (channel, callback) => {
+  ipc.on(channel, async (event, param) => {
+    const result = await callback(event, channel, param)
+    _echo(event, channel, result)
+  })
+}
+
+/**
+ * get api method name
+ * ex.) jsname='user' method='get' => 'user.get'
+ * @param {String} jsname
+ * @param {String} method
+ */
+const getApiName = (jsname, method) => {
+  return jsname + '.' + method;
+}
+
+/**
+ * 加载所有的主程序
+ */
+exports.setup = () => {
+  const ipcDir = path.normalize(__dirname + '/../ipc');
+  fs.readdirSync(ipcDir).forEach(function (filename) {
+    if (path.extname(filename) === '.js' && filename !== 'index.js') {
+      const name = path.basename(filename, '.js');
+      const fileObj = require(`../ipc/${filename}`);
+      _.map(fileObj, function(fn, method) {
+        let methodName = getApiName(name, method);
+        answerRenderer(methodName, fn);
+      });
+    }
+  })
+}
+
+
+
+// exports.setup = () => {
+//   const ipcDir = path.normalize(__dirname + '/../ipc');
+
+//   fs.readdirSync(ipcDir).forEach(function (filename) {
+//     if (path.extname(filename) === '.js' && filename !== 'index.js') {
+//       const filePath = path.join(ipcDir, filename)
+//       require(filePath)
+//     }
+//   })
+// }

+ 3 - 3
electron/setup.js

@@ -1,10 +1,10 @@
 'use strict';
 
-const storage = require('./lib/storage');
-const config = require('./config');
 const is = require('electron-is');
+const config = require('./config');
+const storage = require('./lib/storage');
 const api = require('./lib/api');
-const ipc = require('./ipc');
+const ipc = require('./lib/ipcMain');
 const eLogger = require('./lib/eLogger');
 const crash = require('./lib/crashReport');
 

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

@@ -11,6 +11,9 @@ const mainApi = {
   autoLaunchIsEnabled: '/api/v1/example/autoLaunchIsEnabled',
   openSoftware: '/api/v1/example/openSoftware',
   selectFileDir: '/api/v1/example/selectFileDir',
+  messageShow: '/api/v1/example/messageShow',
+  messageShowConfirm: '/api/v1/example/messageShowConfirm',
+  testElectronApi: '/api/v1/example/testElectronApi',
 }
 
 /**

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

@@ -43,6 +43,11 @@ export const constantRouterMap = [
             name: 'DemoSystemIndex',
             component: () => import('@/views/demo/system/Index')
           },
+          {
+            path: '/demo/testapi/index',
+            name: 'DemoTestApiIndex',
+            component: () => import('@/views/demo/testapi/Index')
+          },
         ]  
       },
       {

+ 7 - 1
frontend/src/layouts/DemoMenu.vue

@@ -48,12 +48,18 @@ export default {
           pageName: 'DemoSystemIndex',
           params: {}
         },
-        'menu_900' : {
+        'menu_800' : {
           icon: 'profile',
           title: '快捷键',
           pageName: 'DemoShortcutIndex',
           params: {}
         },
+        'menu_900' : {
+          icon: 'profile',
+          title: '测试',
+          pageName: 'DemoTestApiIndex',
+          params: {}
+        },
       }
     };
   },

+ 3 - 3
frontend/src/main.js

@@ -4,15 +4,15 @@ import 'ant-design-vue/dist/antd.less';
 import App from './App'
 import router from './router'
 import { VueAxios } from './utils/request'
-import InjectIpc from '@/utils/injectIpc'
+import IpcRenderer from '@/utils/ipcRenderer'
 import HotKeyInput from '@/utils/shortcut/index.js'
 
 Vue.use(antd)
 // mount axios to `Vue.$http` and `this.$http`
 Vue.use(VueAxios)
 // 全局注入IPC
-Vue.use(InjectIpc)
-
+Vue.use(IpcRenderer)
+// 快捷键框组件
 Vue.use(HotKeyInput)
 
 Vue.config.productionTip = false

+ 4 - 3
frontend/src/utils/injectIpc.js → frontend/src/utils/ipcRenderer.js

@@ -11,16 +11,17 @@ const callMain = (ipc, channel, param) => {
   return new Promise((resolve) => {
     // 声明渲染进程函数, 用于主进程函数回调, 返回数据
     // 调用主进程函数
-    ipc.send(channel, param)
-    ipc.once(channel, (event, result) => {
+    ipc.on(channel, (event, result) => {
+      console.log('[ipcRenderer] [callMain] result:', result)
       resolve(result)
     })
+    ipc.send(channel, param)
   })
 }
 
 export default {
   install(Vue) {
     Vue.prototype.$ipc = ipc // 全局注入ipc
-    Vue.prototype.$callMain = (channel, param) => callMain(ipc, channel, param) // 全局注入调用主进程函数的方法
+    Vue.prototype.$ipcCallMain = (channel, param) => callMain(ipc, channel, param) // 全局注入调用主进程函数的方法
   }
 }

+ 1 - 1
frontend/src/utils/request.js

@@ -7,7 +7,7 @@ import { VueAxios } from './axios'
 const request = axios.create({
   // API 请求的默认前缀
   baseURL: process.env.VUE_APP_API_BASE_URL,
-  timeout: 6000 // 请求超时时间
+  timeout: 60000 // 请求超时时间
 })
 
 // 异常拦截处理器

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

@@ -1,68 +0,0 @@
-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)
-}
-  

+ 66 - 12
frontend/src/views/demo/file/Index.vue

@@ -35,19 +35,16 @@
     </div>
     <div class="one-block-1">
       <span>
-        2. 打开文件夹
+        2. 系统原生对话框
       </span>
     </div>  
     <div class="one-block-2">
-      <a-list :grid="{ gutter: 16, column: 4 }" :data-source="file_list">
-        <a-list-item slot="renderItem" slot-scope="item" @click="openDirectry(item.id)">
-          <a-card :title="item.content">
-            <a-button type="link">
-              打开
-            </a-button>
-          </a-card>
-        </a-list-item>
-      </a-list>
+      <a-space>
+        <a-button @click="messageShow('ipc')">消息提示(ipc)</a-button>
+        <a-button @click="messageShowConfirm('ipc')">消息提示与确认(ipc)</a-button>
+        <a-button @click="messageShow('http')">消息提示(http)</a-button>
+        <a-button @click="messageShowConfirm('http')">消息提示与确认(http)</a-button>
+      </a-space>
     </div>
     <div class="one-block-1">
       <span>
@@ -65,6 +62,24 @@
           </a-button>
         </a-col>
       </a-row>
+    </div>      
+    <div class="one-block-1">
+      <span>
+        4. 打开文件夹
+      </span>
+    </div>  
+    <div class="one-block-2">
+      <a-list :grid="{ gutter: 16, column: 4 }" :data-source="file_list">
+        <a-list-item slot="renderItem" slot-scope="item" @click="openDirectry(item.id)">
+          <a-card :title="item.content">
+            <a-button type="link">
+              打开
+            </a-button>
+          </a-card>
+        </a-list-item>
+      </a-list>
+    </div>
+    <div class="footer">
     </div>
   </div>
 </template>
@@ -137,17 +152,53 @@ export default {
         this.$message.error(`${info.file.name} file upload failed.`);
       }
     },
-		selectDir() {
+    selectDir() {
       localApi('selectFileDir', {}).then(res => {
         if (res.code !== 0) {
           return false
         }
-				console.log('res.data.dir:', res.data.dir)
+        console.log('res.data.dir:', res.data.dir)
         this.dir_path = res.data.dir;
       }).catch(err => {
         this.$message.error('异常')
       })
     },
+		messageShow(type) {
+      const self = this;
+      console.log('[messageShow] type:', type)
+      if (type == 'http') {
+        localApi('messageShow', {}).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+          console.log('res:', res)
+        }).catch(err => {
+          self.$message.error(err + '异常')
+        })
+      } else { 
+        self.$ipcCallMain('example.messageShow', '').then(r => {
+          self.$message.info(r);
+        })
+      }
+    },    
+    messageShowConfirm(type) {
+      const self = this;
+      console.log('[messageShowConfirm] type:', type)
+      if (type == 'http') {
+        localApi('messageShowConfirm', {}).then(res => {
+          if (res.code !== 0) {
+            return false
+          }
+          console.log('res:', res)
+        }).catch(err => {
+          self.$message.error(err + '异常')
+        })
+      } else {
+        self.$ipcCallMain('example.messageShowConfirm', '').then(r => {
+          self.$message.info(r);
+        })
+      }
+    },
   }
 };
 </script>
@@ -163,5 +214,8 @@ export default {
   .one-block-2 {
     padding-top: 10px;
   }
+  .footer {
+    padding-top: 10px;
+  }
 }
 </style>

+ 32 - 3
frontend/src/views/demo/socket/Index.vue

@@ -28,6 +28,18 @@
         </a-input-search>
       </a-list>
     </div>
+    <div class="one-block-1">
+      <span>
+        3. 长消息: 服务端持续向前端页面发消息
+      </span>
+    </div>  
+    <div class="one-block-2">
+      <a-space>
+        <a-button @click="socketMsgStart">开始</a-button>
+        <a-button @click="socketMsgStop">结束</a-button>
+        结果:{{ socketMessageString }}
+      </a-space>
+    </div>
   </div>
 </template>
 <script>
@@ -37,13 +49,14 @@ export default {
     return {
       content: 'hello',
       content2: 'hello world',
-      reply: ''
+      reply: '',
+      socketMessageString: ''
     }
   },
   methods: {
     helloHandle(value) {
       const self = this;
-      this.$callMain('example.hello', value).then(r => {
+      this.$ipcCallMain('example.hello', value).then(r => {
         self.$message.info(r);
       })
     },
@@ -55,7 +68,23 @@ export default {
       }).catch(err => {
         console.log('err:', err)
       })
-    }
+    },
+    socketMsgStart() {
+      const self = this;
+      self.$ipc.on('example.socketMessageStart', (event, result) => {
+        console.log('[ipcRenderer] [socketMsgStart] result:', result)
+        self.socketMessageString = result;
+      })
+      self.$ipc.send('example.socketMessageStart', '时间')
+    },
+    socketMsgStop() {
+      const self = this;
+      self.$ipc.on('example.socketMessageStop', (event, result) => {
+        console.log('[ipcRenderer] [socketMsgStop] result:', result)
+        self.socketMessageString = result;
+      })
+      self.$ipc.send('example.socketMessageStop', '')
+    },
   }
 }
 </script>

+ 51 - 0
frontend/src/views/demo/testapi/Index.vue

@@ -0,0 +1,51 @@
+<template>
+  <div id="app-demo-test-api">
+    <div class="one-block-1">
+      <span>
+        1. 测试一些操作系统api
+      </span>
+    </div>  
+    <div class="one-block-2">
+      <a-button @click="exec(1)"> 点击 </a-button>
+    </div>
+  </div>
+</template>
+<script>
+import { localApi } from '@/api/main'
+
+export default {
+  data() {
+    return {
+      type: 1,
+    };
+  },
+  methods: {
+    exec (id) {
+      const params = {
+        id: id
+      }
+			localApi('testElectronApi', params).then(res => {
+				if (res.code !== 0) {
+					return false
+				}
+			}).catch(err => {
+				console.log('err:', err)
+			})
+    },
+  }
+};
+</script>
+<style lang="less" scoped>
+#app-demo-test-api {
+  padding: 0px 10px;
+  text-align: left;
+  width: 100%;
+  .one-block-1 {
+    font-size: 16px;
+    padding-top: 10px;
+  }
+  .one-block-2 {
+    padding-top: 10px;
+  }
+}
+</style>

+ 6 - 4
update.md

@@ -3,10 +3,12 @@
 2. demo增加分类
 3. demo界面优化,重新排版
 4. 增加选择文件夹目录
-5. 修复拉伸窗口空白
-6. 路由分类
-7. 删除非必要代码
-8. 软件名称统一
+5. 增加ipc通信模块与apis模块语法统一
+6. 增加路由分类
+7. 增加ipc支持长通信,服务端持续向页面发消息
+8. 增加操作系统弹框demo
+9. 删除非必要代码
+10. 修复拉伸窗口空白
 
 ## 1.13.0
 1. 修复自动更新