# 业务项目整合

# 初步整合

  • 导入前端Vue3的项目到Rnederer文件夹下;

  • 修改tsconfig.json文件

    {
      "extends": "../../tsconfig.json",
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@/*": ["./src/*"]
        },
        "lib": ["ESNext", "dom", "dom.iterable"],
        "jsx": "preserve",
        "resolveJsonModule": true,
        "allowJs": true,
        "noImplicitAny": false
      },
    
      "include": [
        "src/**/*.vue",
        "src/**/*.ts",
        "src/**/*.tsx",
        "types/**/*.d.ts",
        "../../types/**/*.d.ts",
        "../preload/types/electron-api.d.ts"
      ]
    }
    
  • 导入资源文件夹public,配置index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:" />
        <meta content="width=device-width, initial-scale=1.0" name="viewport" />
        <title>toimc社区</title>
        <link rel="stylesheet" href="./public/layui/layui.css" />
      </head>
      <body>
        <div id="app"></div>
        <script src="./src/index.ts" type="module"></script>
      </body>
    </html>
    
  • 更新项目依赖package.json

      "devDependencies": {
        "@types/axios": "^0.14.0",
        "@types/electron-devtools-installer": "2.2.0",
        "@types/i18n": "^0.13.1",
        "@types/lodash": "^4.14.168",
        "@types/node": "^14.14.31",
        "@types/uuid": "^8.3.0",
        "@types/yup": "^0.29.11",
        "@typescript-eslint/eslint-plugin": "5.3.0",
        "@typescript-eslint/parser": "^5.3.0",
        "@vitejs/plugin-vue": "1.9.4",
        "cross-env": "7.0.3",
        "electron": "15.3.0",
        "electron-builder": "22.13.1",
        "electron-devtools-installer": "3.2.0",
        "eslint": "8.1.0",
        "eslint-config-prettier": "^8.3.0",
        "eslint-plugin-prettier": "^4.0.0",
        "eslint-plugin-vue": "8.0.3",
        "lint-staged": "11.2.6",
        "playwright": "1.16.3",
        "prettier": "^2.4.1",
        "sass": "^1.43.4",
        "simple-git-hooks": "2.7.0",
        "typescript": "4.4.4",
        "vite": "2.6.13",
        "vue-tsc": "0.28.10"
      },
      "dependencies": {
        "@vee-validate/i18n": "^4.1.20",
        "@vee-validate/rules": "^4.1.20",
        "axios": "^0.21.1",
        "dayjs": "^1.10.4",
        "electron-updater": "4.6.1",
        "i18n": "^0.13.3",
        "qs": "^6.9.6",
        "uuid": "^8.3.2",
        "vee-validate": "^4.0.4",
        "vue": "3.2.21",
        "vue-router": "4.0.12",
        "vuex": "^4.0.2",
        "yup": "^0.32.9"
      }
    
  • 修改服务端的端口renderer/vite.config.js,这样可以请求到正确的后台API接口:

    /**
     * @type {import('vite').UserConfig}
     * @see https://vitejs.dev/config/
     */
    const config = {
      //..
      server: {
        fs: {
          strict: true
        },
        port: 8000
      },
      // ...
    }
    
    export default config
    

# Electron中的菜单

# 顶部菜单

修改main/src/index.ts文件:

const isSingleInstance = app.requestSingleInstanceLock();
const isMac = process.platform === 'darwin'

const template: MenuItemConstructorOptions[] = [
  { role: 'appMenu' },
  // ...(isMac ? [{
  //   label: app.name,
  //   submenu: [
  //      这里还有一个知识,accelerator是快捷键的设置
  //     { role: 'about', label: '关于我们', accelerator: 'CmdOrCtrl+1' },
  //     { type: 'separator' },
  //     { role: 'services' },
  //     { type: 'separator' },
  //     { role: 'hide' },
  //     { role: 'hideOthers' },
  //     { role: 'unhide' },
  //     { type: 'separator' },
  //     { role: 'quit' }
  //   ]
  // }] as MenuItemConstructorOptions[] : []),
  { role: 'fileMenu' },
  { role: 'editMenu' },
  { role: 'viewMenu' },
  { role: 'windowMenu' },
  {
    role: 'help',
    submenu: [
      {
        label: '查看帮助',
        click: async () => {
          await shell.openExternal('https://front-end.toimc.com')
        }
      }
    ]
  }
]


// 设置electron应用的菜单
const menu = Menu.buildFromTemplate(template)

Menu.setApplicationMenu(menu)

注意:

  • role是内置的一些默认的菜单项;
  • submenu是自定义的菜单项;
  • type: 'separator'是一个分隔符;

# Docker菜单

说明:Docker菜单适合于macOS!

修改main/src/index.ts文件:

// 平台判断
const isMac = process.platform === 'darwin'

const dockerMenu: MenuItemConstructorOptions[] = [{
  label: 'Docker',
  submenu: [
    {
      label: 'sub1',
      click: () => {
        console.log('from sub1')
      }
    },
    {
      label: 'sub2'
    },
  ]
}]

const dockMenu = Menu.buildFromTemplate(dockerMenu)

// Install "Vue.js devtools"
if (import.meta.env.MODE === 'development') {
  app.whenReady()
    .then(() => import('electron-devtools-installer'))
    .then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
      loadExtensionOptions: {
        allowFileAccess: true,
      },
    }))
    .then(() => {
      if (isMac) {
        // 加入dock菜单
        app.dock.setMenu(dockMenu)
      }
    })
    .catch(e => console.error('Failed install extension:', e));
}

# 右键菜单(原生)

需要使用到ipc的通信:

为了保证安全,在preload/src/index.ts中注册Ipc事件:

import type { IpcRendererEvent} from 'electron';
import {contextBridge, ipcRenderer} from 'electron';


const _ipcRenderer = {
  // 暴露出去的ipcRenderer上的API
  on: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void)=> {
    ipcRenderer.on(channel, listener);
    return _ipcRenderer
  },
  send: (channel: string, ...args: any[]) => {
    ipcRenderer.send(channel, ...args);
  }
}

const apiKey = 'electron';
/**
 * @see https://github.com/electron/electron/issues/21437#issuecomment-573522360
 */
const api: ElectronApi = {
  versions: process.versions,
  ipcRenderer: _ipcRenderer
};

/**
 * The "Main World" is the JavaScript context that your main renderer code runs in.
 * By default, the page you load in your renderer executes code in this world.
 *
 * @see https://www.electronjs.org/docs/api/context-bridge
 */
contextBridge.exposeInMainWorld(apiKey, api);

// window.electron.versions

在主进程中进行注册,文件main/src/index.ts

ipcMain.on('show-context-menu', (event) => {
  console.log('🚀 ~ file: index.ts ~ line 172 ~ ipcMain.on ~ event', event)
  const win = BrowserWindow.fromWebContents(event.sender)
  if (win) {

    const clickMenu: MenuItemConstructorOptions[] = [{
      label: 'Docker',
      submenu: [
        {
          label: 'sub1',
          click: () => {
            event.sender.send('click-pop-menu', {
              'event': 'sub1-clicked',
              'data': 'hello'
            })
          }
        },
        {
          label: 'sub2'
        },
      ]
    }]
    const popMenu = Menu.buildFromTemplate(clickMenu)
    popMenu.popup({window: win});
  }
})

渲染进程中:

// preload会加载,添加到window[apiKey]
const {
  ipcRenderer: { send, on }
} = window.electron

window.addEventListener('contextmenu', (e) => {
  e.preventDefault()
  // 2.ipcRenderer send -> menu请求
  send('show-context-menu')
})

效果:

image-20220131111024777

# 快捷键

快捷键可以包含多个功能键和一个键码的字符串,由符号+结合,用来定义你应用中的键盘快捷键

示例

  • CommandOrControl+A
  • CommandOrControl+Shift+Z

快捷方式使用 register (opens new window) 方法在 globalShortcut (opens new window) 模块中注册, 即:

const { app, globalShortcut } = require('electron')

app.whenReady().then(() => {
  // 注册一个 'v' 快捷键监听器.
  // 全屏控制
  globalShortcut.register('CmdOrCtrl+1', () => {
    const win = BrowserWindow.getFocusedWindow()
    win?.setFullScreen(!win?.isFullScreen())
    
    // 还可以尝试 app.showAboutPannel() 这个就是关于我们
  })
})

通过:

  • 菜单来查看设置是否成功:

image-20220131110012787

  • 还可以通过API:globalShortcut.isRegistered(accelerator)来判断注册是否成功;

# 跨平台提醒

在 Linux 和 Windows 上, Command 键没有任何效果, 所以使用 CommandOrControl表述, macOS 是 Command ,在 Linux 和 Windows 上是Control

使用 Alt按键替代 Option按键。 使用 Alt 键代替Option. Option 键只在 macOS 系统上存在, 而 Alt 键在任何系统上都有效。

Super (或 Meta) 键对应Windows 和 Linux 系统上的 Windows 键,但在 macOS 里为 Cmd 键。

# 可用的功能键

  • Command (缩写为Cmd)
  • Control (缩写为Ctrl)
  • CommandOrControl (缩写为 CmdOrCtrl)
  • Alt
  • Option
  • AltGr
  • Shift
  • Super
  • 元数据

# 可用的普通按键

  • 09
  • AZ
  • F1F24
  • 类似~, !, @, #, $的标点符号
  • Plus
  • Space
  • Tab
  • 大写锁定(Capslock)
  • 数字锁定(Numlock)
  • 滚动锁定
  • Backspace
  • 删除
  • Insert
  • Return (等同于 Enter)
  • Up, Down, Left and Right
  • HomeEnd
  • PageUpPageDown
  • Escape (缩写为 Esc)
  • VolumeUp, VolumeDownVolumeMute
  • MediaNextTrackMediaPreviousTrackMediaStopMediaPlayPause
  • PrintScreen
  • 小键盘按键
    • num1-num9 -数字1-数字9
    • numdec - 小数点
    • numadd - 加号
    • numsub - 减号
    • nummult - 乘号
    • numdiv - 除号

# 国际化

Electron默认的菜单是英文的,很多时候国内的应用场景都需要有中文的菜单,这个时候就需要国际化了。常用的方案就是i18n,见官网 (opens new window)

步骤如下:

  • 安装依赖:
npm i -S i18n
  • 创建对应的locales目录:

image-20220201095442087

  • 添加对应的译文json文件:
{
  "paste": "粘贴"
}
  • 修改main/index.ts:
import i18n from 'i18n'

// 国际化
i18n.configure({
  locales: ['zh-CN'],
  directory: join(__dirname, '../locales'),
})

app.whenReady()
  .then(() => {
    createWindow()
    // 国际化
    // 方法一:
    // const locale = app.getLocale()
    // i18n.setLocale(locale)
  
    // 方法二:
    i18n.setLocale('en') // 这里的设置要与自己的json文件名对应
    console.log(i18n.__('paste'))
  // ...
}