vben-admin 改造爲 electron 版本

vben-admin 改造爲 electron 版本

vben-admin 簡介

https://vvbin.cn/doc-next/

目標

目標:將 vben-admin 改造爲 electron 版本

下載源碼

完整版:

https://github.com/vbenjs/vue-vben-admin

目前最新發布版本:v2.8.0

https://github.com/vbenjs/vue-vben-admin/releases/tag/v2.8.0

輕量版:

https://github.com/vbenjs/vben-admin-thin-next

目前最新發布版本:v2.7.2

安裝依賴

下載後運行:

E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0>pnpm install
 ERR_PNPM_INVALID_OVERRIDE_SELECTOR  Cannot parse the "//" selector in the overrides

出現錯誤,安裝失敗。

package.json 裏面的

  "resolutions": {
    "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
    "bin-wrapper": "npm:bin-wrapper-china",
    "rollup": "^2.56.3"
  },

"//": 一行去掉,

pnpm-lock.yaml 裏面的

overrides:
  //: Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it
  bin-wrapper: npm:bin-wrapper-china
  rollup: ^2.56.3

"//": 一行去掉,

再次運行如下命令進行安裝:

E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0>pnpm install

下載完,有一個提示錯誤:

> [email protected] prepare E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0
> husky install

fatal: not a git repository (or any of the parent directories): .git

運行如下命令:

E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0>git init
Initialized empty Git repository in E:/artisan/labs/vben-admin-electron/src/vue-vben-admin-2.8.0/.git/

再次運行如下命令進行安裝:

E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0>pnpm install

運行

運行如下命令進行啓動運行

E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0>pnpm serve

查看運行結果:

> npm run dev


> [email protected] dev
> vite

Pre-bundling dependencies:
  vue
  pinia
  vue-router
  vue-i18n
  ant-design-vue
  (...and 72 more)
(this will be run only when your dependencies or config have changed)

  vite v2.6.13 dev server running at:

  > Network:  http://192.168.0.102:3100/
  > Local:    http://localhost:3100/    
  > Network:  http://172.25.32.1:3100/  
  > Network:  http://172.23.144.1:3100/ 
  > Network:  http://172.27.112.1:3100/ 
  > Network:  http://172.28.96.1:3100/  

訪問:http://localhost:3100

賬號密碼是預先好的,直接點擊登錄按鈕。

electron 版本

安裝依賴

devDependencies

pnpm install electron --save-dev
pnpm install electron-builder --save-dev
pnpm install electron-connect --save-dev
pnpm install electron-contextmenu-middleware --save-dev
pnpm install electron-input-menu --save-dev
pnpm install wait-on --save-dev

下面依次安裝,先安裝 electron

> pnpm install  electron --save-dev

devDependencies:
+ electron 19.0.6

 WARN  Issues with peer dependencies found
.
├─┬ rollup-plugin-visualizer
│ └── ✕ missing peer rollup@^2.0.0
└─┬ vite-plugin-mock
  └─┬ @rollup/plugin-node-resolve
    ├── ✕ missing peer rollup@^2.42.0
    └─┬ @rollup/pluginutils
      └── ✕ missing peer rollup@^1.20.0||^2.0.0
Peer dependencies that should be installed:
  rollup@">=2.42.0 <3.0.0">

有警告,安裝 rollup ,運行如下命令進行安裝:

> pnpm install rollup

安裝完成後,提示安裝瞭如下依賴:

dependencies:
+ rollup 2.59.0 (2.75.7 is available)

devDependencies:
- rollup-plugin-visualizer 5.5.2
+ rollup-plugin-visualizer 5.5.2
- vite-plugin-mock 2.9.6
+ vite-plugin-mock 2.9.6

安裝 electron-builder

> pnpm install electron-builder --save-dev
...
devDependencies:
+ electron-builder 23.1.0
...

安裝 electron-connect

> pnpm install electron-connect --save-dev
devDependencies:
+ electron-connect 0.6.3

安裝 electron-contextmenu-middleware

> pnpm install electron-contextmenu-middleware --save-dev
devDependencies:
+ electron-contextmenu-middleware 1.0.3

安裝 electron-input-menu

> pnpm install electron-input-menu --save-dev
devDependencies:
+ electron-input-menu 2.1.0

安裝 electron-input-menu

> pnpm install wait-on --save-dev
evDependencies:
+ wait-on 6.0.1

dependencies

pnpm install electron-is-dev

pnpm install rollup
pnpm install rollup-plugin-esbuild

pnpm install @rollup/plugin-alias
pnpm install @rollup/plugin-commonjs
pnpm install @rollup/plugin-json
pnpm install @rollup/plugin-node-resolve

pnpm install esbuild
pnpm install chalk
pnpm install install ora

下面依次安裝:

> pnpm install electron-is-dev
dependencies:
+ electron-is-dev 2.0.0

> pnpm install rollup
dependencies:
+ rollup 2.59.0 (2.75.7 is available)

devDependencies:
- rollup-plugin-visualizer 5.5.2
+ rollup-plugin-visualizer 5.5.2
- vite-plugin-mock 2.9.6
+ vite-plugin-mock 2.9.6

> pnpm install rollup-plugin-esbuild
dependencies:
+ rollup-plugin-esbuild 4.9.1

 WARN  Issues with peer dependencies found
.
└─┬ @rollup/plugin-commonjs
  └── ✕ unmet peer rollup@^2.68.0: found 2.59.0
  
> pnpm install @rollup/plugin-alias
dependencies:
+ @rollup/plugin-alias 3.1.9

> pnpm install @rollup/plugin-commonjs
dependencies:
+ @rollup/plugin-commonjs 22.0.0
 WARN  Issues with peer dependencies found
.
└─┬ @rollup/plugin-commonjs
  └── ✕ unmet peer rollup@^2.68.0: found 2.59.0

> pnpm install @rollup/plugin-json
dependencies:
+ @rollup/plugin-json 4.1.0

 WARN  Issues with peer dependencies found
.
└─┬ @rollup/plugin-commonjs
  └── ✕ unmet peer rollup@^2.68.0: found 2.59.0

> pnpm install @rollup/plugin-node-resolve
dependencies:
+ @rollup/plugin-node-resolve 13.3.0

 WARN  Issues with peer dependencies found
.
└─┬ @rollup/plugin-commonjs
  └── ✕ unmet peer rollup@^2.68.0: found 2.59.0

----------------------------------------
## 上面的安裝幾個依賴包都提示:
## ✕ unmet peer rollup@^2.68.0: found 2.59.0
## 這裏重新更新下 rollup的版本
> pnpm install rollup@^2.68.0

## 更新了各個版本,輸出結果如下:
dependencies:
- @rollup/plugin-alias 3.1.9
+ @rollup/plugin-alias 3.1.9
- @rollup/plugin-commonjs 22.0.0
+ @rollup/plugin-commonjs 22.0.0
- @rollup/plugin-json 4.1.0
+ @rollup/plugin-json 4.1.0
- @rollup/plugin-node-resolve 13.3.0
+ @rollup/plugin-node-resolve 13.3.0
- rollup 2.59.0
+ rollup 2.68.0 (2.75.7 is available)
- rollup-plugin-esbuild 4.9.1
+ rollup-plugin-esbuild 4.9.1

devDependencies:
- rollup-plugin-visualizer 5.5.2
+ rollup-plugin-visualizer 5.5.2
- vite-plugin-mock 2.9.6
+ vite-plugin-mock 2.9.6

----------------------------------------
> pnpm install esbuild
dependencies:
+ esbuild 0.14.47

> pnpm install chalk
dependencies:
+ chalk 5.0.1

> pnpm install install ora
dependencies:
+ install 0.13.0
+ ora 6.1.0

集成electron代碼

index.ts

新建 electron-main 文件夾,新建 index.ts 文件

注意:
是項目根目錄下,即:vue-vben-admin-2.8.0/electron-main

代碼清單: electron-main/index.ts

import { app, BrowserWindow, screen } from 'electron';
import is_dev from 'electron-is-dev';
import { join } from 'path';

let mainWindow: BrowserWindow | null = null;

class createWin {
  constructor() {
    const displayWorkAreaSize = screen.getAllDisplays()[0].workArea;
    mainWindow = new BrowserWindow({
      width: parseInt(`${displayWorkAreaSize.width * 0.85}`, 10),
      height: parseInt(`${displayWorkAreaSize.height * 0.85}`, 10),
      movable: true,
      // frame: false,
      show: false,
      center: true,
      resizable: true,
      // transparent: true,
      titleBarStyle: 'default',
      webPreferences: {
        devTools: true,
        contextIsolation: false,
        nodeIntegration: true,
        //enableRemoteModule: true,
        webSecurity: false, //解決:打包後出現跨域問題
      },
      backgroundColor: '#fff',
    });
    const URL = is_dev
      ? `http://localhost:${process.env.PORT}` // vite 啓動的服務器地址
      : `file://${join(__dirname, '../index.html')}`; // vite 構建後的靜態文件地址

    mainWindow.loadURL(URL);

    mainWindow.on('ready-to-show', () => {
      mainWindow.show();
    });
  }
}

app.whenReady().then(() => new createWin());

const isFirstInstance = app.requestSingleInstanceLock();

if (!isFirstInstance) {
  app.quit();
} else {
  app.on('second-instance', () => {
    if (mainWindow) {
      mainWindow.focus();
    }
  });
}

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    new createWin();
  }
});

rollupElectronConfig.ts

在目錄 build/config 目錄下新建 rollupElectronConfig.ts

代碼清單:build/config/rollupElectronConfig.ts

import path from 'path';
import { RollupOptions } from 'rollup';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import esbuild from 'rollup-plugin-esbuild';
import alias from '@rollup/plugin-alias';
import json from '@rollup/plugin-json';

export function getRollupOptions(): RollupOptions {
  return {
    input: path.join(__dirname, '../../electron-main/index.ts'),
    output: {
      file: path.join(__dirname, '../../dist/main/build.js'),
      format: 'cjs',
      name: 'ElectronMainBundle',
      sourcemap: true,
    },
    plugins: [
      nodeResolve({ preferBuiltins: true, browser: true }), // 消除碰到 node.js 模塊時⚠警告
      commonjs(),
      json(),
      esbuild({
        // All options are optional
        include: /\.[jt]sx?$/, // default, inferred from `loaders` option
        exclude: /node_modules/, // default
        // watch: process.argv.includes('--watch'), // rollup 中有配置
        sourceMap: false, // default
        minify: process.env.NODE_ENV === 'production',
        target: 'es2017', // default, or 'es20XX', 'esnext'
        jsxFactory: 'React.createElement',
        jsxFragment: 'React.Fragment',
        // Like @rollup/plugin-replace
        define: {
          __VERSION__: '"x.y.z"',
        },
        // Add extra loaders
        loaders: {
          // Add .json files support
          // require @rollup/plugin-commonjs
          '.json': 'json',
          // Enable JSX in .js files too
          '.js': 'jsx',
        },
      }),
      alias({
        entries: [{ find: '/@main/', replacement: path.join(__dirname, '../../electron-main') }],
      }),
    ],
    external: [
      'crypto',
      'assert',
      'fs',
      'util',
      'os',
      'events',
      'child_process',
      'http',
      'https',
      'path',
      'electron',
    ],
  };
}

compilerElectron.ts

在 build/script 目錄下新建 compilerElectron.ts

代碼清單: build/script/compilerElectron.ts

import rollup, { OutputOptions } from 'rollup';
import chalk from 'chalk';
import ora from 'ora';
import waitOn from 'wait-on';
import net from 'net';
import { URL } from 'url';
import minimist from 'minimist';
import electronConnect from 'electron-connect';

import { getRollupOptions } from '../config/rollupElectronConfig';

const argv = minimist(process.argv.slice(2));
const TAG = '[compiler-electron]';

export function startCompilerElectron(port = 80) {
  // 因爲 vite 不會重定向到 index.html,所以直接寫 index.html 路由。
  const ELECTRON_URL = `http://localhost:${port}/index.html`;

  const spinner = ora(`${TAG} Electron build...`);
  const electron = electronConnect.server.create({ stopOnClose: true });
  const rollupOptions = getRollupOptions();

  function watchFunc() {
    // once here, all resources are available
    const watcher = rollup.watch(rollupOptions);
    watcher.on('change', (filename) => {
      const log = chalk.green(`change -- ${filename}`);
      console.log(TAG, log);
    });
    watcher.on('event', (ev) => {
      if (ev.code === 'END') {
        // init-未啓動、started-第一次啓動、restarted-重新啓動
        electron.electronState === 'init' ? electron.start() : electron.restart();
      } else if (ev.code === 'ERROR') {
        console.log(ev.error);
      }
    });
  }

  if (argv.watch) {
    waitOn(
      {
        resources: [ELECTRON_URL],
        timeout: 5000,
      },
      (err) => {
        if (err) {
          const { hostname } = new URL(ELECTRON_URL);
          const serverSocket = net.connect(port, hostname, () => {
            watchFunc();
          });
          serverSocket.on('error', (e) => {
            console.log(err);
            console.log(e);
            process.exit(1);
          });
        } else {
          watchFunc();
        }
      },
    );
  } else {
    spinner.start();
    rollup
      .rollup(rollupOptions)
      .then((build) => {
        spinner.stop();
        console.log(TAG, chalk.green('Electron build successed.'));
        build.write(rollupOptions.output as OutputOptions);
      })
      .catch((error) => {
        spinner.stop();
        console.log(`\n${TAG} ${chalk.red('構建報錯')}\n`, error, '\n');
      });
  }
}

startElectron.ts

在 build/script 目錄下新建 startElectron.ts
代碼清單: build/script/startElectron.ts

import { createServer } from 'vite';
import path from 'path';
import { startCompilerElectron } from './compilerElectron';
import minimist from 'minimist';

(async () => {
  const argv = minimist(process.argv.slice(2));
  console.log(argv);
  const isDev = argv.env === 'development';
  let port: number | undefined = undefined;
  if (isDev) {
    const server = await createServer({
      root: path.resolve(__dirname, '../../'),
    });

    const app = await server.listen();
    port = app.config.server.port;
    process.env.PORT = `${port}`;
  }

  startCompilerElectron(port);
})();

命令

在 package.json 添加如下命令

{
  ...
  "main": "dist/main/build.js",
  "build": {
    "appId": "[email protected]",
    "electronDownload": {
      "mirror": "https://npm.taobao.org/mirrors/electron/"
    },
    "files": [
      "!node_modules",
      "dist/**",
      "node_modules/serialport"
    ],
    "asar": false,
    "mac": {
      "artifactName": "${productName}_setup_${version}.${ext}",
      "target": [
        "dmg"
      ]
    },
    "linux": {
      "icon": "build/icons/512x512.png",
      "target": [
        "deb"
      ]
    },
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_setup_${version}.${ext}"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "deleteAppDataOnUninstall": false
    }
 },
 {
    "main": "dist/main/build.js",
    "build": {
    "appId": "[email protected]",
    "electronDownload": {
      "mirror": "https://npm.taobao.org/mirrors/electron/"
    },
    "files": [
      "!node_modules",
      "dist/**",
      "node_modules/serialport"
    ],
    "asar": false,
    "mac": {
      "artifactName": "${productName}_setup_${version}.${ext}",
      "target": [
        "dmg"
      ]
    },
    "linux": {
      "icon": "build/icons/512x512.png",
      "target": [
        "deb"
      ]
    },
    "win": {
      "target": [
        {
          "target": "nsis",
          "arch": [
            "x64"
          ]
        }
      ],
      "artifactName": "${productName}_setup_${version}.${ext}"
    },
    "nsis": {
      "oneClick": false,
      "perMachine": false,
      "allowToChangeInstallationDirectory": true,
      "deleteAppDataOnUninstall": false
    }
  },
  "scripts": {
     ...
    "start": "node ./build/script/electron/dev",
    "dev:app": "esno ./build/script/startElectron.ts --env=development --watch",
    "build:app": "npm run build && esno ./build/script/startElectron.ts --env=production && electron-builder ",
    }
   ...
} 
  • main:electron程序入口
  • build:打包
  • scripts:electron 相關命令,
    • dev:app:開發環境運行
    • build:app:項目打包

運行 electron

> pnpm dev:app

點擊登錄按鈕:

打包

> pnpm build:app

執行打包命令後,在項目的目錄下,自動生成一個 dist 目錄

出現錯誤 : 找不到文件

打開綠色版,會出現如下錯誤:

圖片、js 的地址錯誤,找不到文件。

打開 .env.production 文件

找到

# public path
VITE_PUBLIC_PATH = /

將其修改爲:

# public path 
## 注意:生產環境發佈爲 Electron 客戶端時得修改爲 ./ ,否則Electron 客戶端找不到文件
VITE_PUBLIC_PATH = ./

重新打包:

> pnpm build:app

打開綠色版,運行一切正常

點擊登錄按鈕:

electron 示例

進程間通信

渲染進程

代碼清單: src/views/electron/process/ipc.vue

<template>
  <PageWrapper
    title="示例:進程間通信"
    contentBackground
    contentClass="p-4"
    content="在這裏將演示如何在主進程與渲染進程間進行通信"
  >
    <Divider>向主進程發送消息</Divider>
    <Alert
      class="mt-4"
      type="info"
      message="點擊按鈕後請查看編譯器(Visual Code)控制檯消息"
      show-icon
    />
    <div class="mt-4">
      <a-button type="primary" size="small" @click="hanleSendMessageToMain">
        向主進程發送消息
      </a-button>
    </div>

    <Divider>向主進程發送消息並接收主進程的消息</Divider>
    <div class="mt-4">
      <a-button type="primary" size="small" @click="hanleSendMessageToMainNeedReply">
        向主進程發送消息並接收主進程的消息
      </a-button>
      <p> 主進程返回的消息: {{ dataFromMain }} </p>
    </div>
  </PageWrapper>
</template>

<script lang="ts">
  import { defineComponent, ref, onMounted } from 'vue';
  import { Alert, Divider } from 'ant-design-vue';
  import { PageWrapper } from '/@/components/Page';
  const { ipcRenderer } = require('electron');
  //import { ipcRenderer } from 'electron'; // 這樣引用,打包後報錯,請使用:require('electron')

  export default defineComponent({
    components: { PageWrapper, Alert, Divider },
    setup() {
      const dataFromMain = ref('');

      const hanleSendMessageToMain = () => {
        ipcRenderer.send('event_from_renderer', { param: 123 });
      };

      const hanleSendMessageToMainNeedReply = () => {
        ipcRenderer.send('event_from_renderer_need_replay', { param: 'abc' });
      };

      const registerEvents = () => {
        ipcRenderer.on('event_from_main_replay', (_, data) => {
          console.log('In renderer:event_from_main_replay-->data:', data);
          dataFromMain.value += data;
        });
      };

      onMounted(() => {
        registerEvents();
      });

      return {
        hanleSendMessageToMain,
        hanleSendMessageToMainNeedReply,
        dataFromMain,
      };
    },
  });
</script>

<style lang="less" scoped></style>

其中,

  • 渲染進程發送消息到主進程

      const hanleSendMessageToMainNeedReply = () => {
            ipcRenderer.send('event_from_renderer_need_replay', { param: 'abc' });
          };
    
  • 監聽來自主進程的響應消息

          const registerEvents = () => {
            ipcRenderer.on('event_from_main_replay', (_, data) => {
              console.log('In renderer:event_from_main_replay-->data:', data);
              dataFromMain.value += data;
            });
          };
    
          onMounted(() => {
            registerEvents();
          });
    

主進程

主進程的代碼寫在 electron-baisc.ts 文件中

代碼清單: electron-main/main/electron-baisc.ts

import { ipcMain } from 'electron';

const registerEvents = () => {
  ipcMain.on('event_from_renderer', (event, data) => {
    console.log('event_from_renderer->data:', data);
  });

  ipcMain.on('event_from_renderer_need_replay', (event, data) => {
    console.log('event_from_renderer_need_replay->data:', data);
    event.reply('event_from_main_replay', '【渲染進程,你的消息我已收到】');
  });
};

export default {
  registerEvents,
};

然後在主進程 index.ts 中使用

代碼清單: electron-main/index.ts

...
import electronBaisc from './main/electron-baisc';

class createWin {
    ...
    mainWindow.on('ready-to-show', () => {
      mainWindow.show();
      registerEvents();
    });
  }
}

//-----------------------------------------------------------------------------------
// 在這個文件中,你可以包含應用程序剩餘的所有部分的代碼,
// 也可以拆分成幾個文件,然後用 require 導入。
//-----------------------------------------------------------------------------------
function registerEvents() {
  electronBaisc.registerEvents();
}

添加路由菜單

新建 electron.ts,系統會根據路由自動生成菜單項

代碼清單:src/routes/modules/electron.ts

import type { AppRouteModule } from '/@/router/types';

import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';

const electron: AppRouteModule = {
  path: '/electron',
  name: 'Electron',
  component: LAYOUT,
  redirect: '/electron/process/ipc',
  meta: {
    orderNo: 1999,
    icon: 'ant-design:share-alt-outlined',
    title: t('routes.electron.RouteName'),
  },
  children: [
    {
      path: 'Process',
      name: 'ElectronProcess',
      component: getParentLayout('ElectronProcess'),
      meta: {
        title: t('routes.electron.Process'),
      },
      redirect: '/electron/process/ipc',
      children: [
        {
          path: 'ipc',
          name: 'ElectronIPC',
          meta: {
            title: t('routes.electron.InterProcessCommunication'),
          },
          component: () => import('/@/views/electron/process/Ipc.vue'),
        },
      ],
    },
  ],
};

export default electron;

其中:

  • routes.electron.xxx: 是支持國際化的字符串

國際化

路由菜單支持國際化,需要新建兩個多語言資源,新建如下兩個文件,新建後系統會自動加載,可以直接使用。

  • 英文

代碼清單:src/locales/lang/en/routes/electron.ts

export default {
  RouteName: 'Electron demo',
  Process: 'Process',
  InterProcessCommunication: 'IPC',
};
  • 簡體中文

代碼清單:src/locales/lang/zh-CN/routes/electron.ts

export default {
  RouteName: 'Electron示例',
  Process: '進程',
  InterProcessCommunication: '進程間通信',
};

測試

運行開發環境

> pnpm dev:app

點擊【 向主進程發送消息 】按鈕,向主進程發送消息,在 Visual Code 的控制檯中可以看到輸出日誌:

event_from_renderer->data: { param: 123 }

點擊【 向主進程發送消息並接收主進程的消息 】按鈕, 向主進程發送消息並接收主進程的消息,如下圖所示:

串口

Github: https://github.com/serialport/node-serialport

官網:https://serialport.io/

文檔:https://serialport.io/docs/

https://serialport.io/docs/guide-electron

其它資料:

使用SerialPort庫進行Node物聯網項目開發:https://zhuanlan.zhihu.com/p/98050314

安裝

https://serialport.io/docs/guide-installation

運行如下命令進行安裝

> pnpm install serialport
dependencies:
+ serialport 10.4.0 #目前最新版本

可能出現的問題

10.x.x 版本已經允許編譯了各個平臺的lib,並支持 Typescript,一般不會出現以下的問題。

10.x.x 版本以下可能會出現 serialport 不能使用的問題

若 serialport 不能使用,重新編譯 serialport 的模塊,這個看情況而定!

安裝:node-v16.14.0-x64.msi

安裝:python-3.10.2-amd64.exe

pnpm install global node-gyp 
pnpm install [email protected] 
pnpm install electron-rebuild
.\node_modules\.bin\electron-rebuild.cmd 

[email protected] 支持 windows 7、8、10+

[email protected] 支持 windows 10+

國際化

路由菜單支持國際化,需要新建兩個多語言資源,新建如下兩個文件,新建後系統會自動加載,可以直接使用。

  • 英文

代碼清單:src/locales/lang/en/routes/electron.ts

export default {
  ...
  SerialPort: 'SerialPort',
  SerialPortAssitant: 'SP-Assitant',
};
  • 簡體中文

代碼清單:src/locales/lang/zh-CN/routes/electron.ts

export default {
  ...
  SerialPort: '串口',
  SerialPortAssitant: '串口助手',
};

添加路由菜單

在 src/routes/modules/electron.ts,添加菜單

代碼清單:src/routes/modules/electron.ts

import type { AppRouteModule } from '/@/router/types';

import { getParentLayout, LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';

const electron: AppRouteModule = {
  path: '/electron',
  name: 'Electron',
  component: LAYOUT,
  redirect: '/electron/process/ipc',
  meta: {
    orderNo: 1999,
    icon: 'ant-design:share-alt-outlined',
    title: t('routes.electron.RouteName'),
  },
  children: [
    {
        // 進程間通信路由
        ...
    },
    {
      path: 'SerialPort',
      name: 'SerialPort',
      component: getParentLayout('SerialPort'),
      meta: {
        title: t('routes.electron.SerialPort'),
      },
      redirect: '/electron/serialport/assistant',
      children: [
        {
          path: 'ipc',
          name: 'SPAssistant',
          meta: {
            title: t('routes.electron.SerialPortAssitant'),
          },
          component: () => import('/@/views/electron/serialport/Assistant.vue'),
        },
      ],
    },
  ],
};

export default electron;

串口的使用示例

代碼清單: src/views/electron/serialport/Assistant.vue

<template>
  <PageWrapper
    title="示例:串口助手"
    contentBackground
    contentClass="p-4"
    content="在這裏將演示如何在Electron中使用 Node-SerialPort, 官網:https://serialport.io/"
  >
    <div>
      <a-alert type="error" v-if="errorMsg" :message="errorMsg" banner closable />
      <a-alert type="success" v-if="message" :message="message" show-icon closable />
      <div style="margin-top: 10px">
        <a-row>
          <a-col :span="18">
            <div>
              <h1>接收緩存區</h1>
              <a-row>
                <textarea cols="80" rows="10" v-model="recievedData"></textarea>
              </a-row>
              <a-row>
                <a-space>
                  <a-button class="" @click="handleClearRecievedData"> 清空 </a-button>
                </a-space>
              </a-row>
            </div>
            <div>
              <h1>發送緩存區</h1>
              <a-row>
                <textarea cols="80" rows="10" v-model="toSendData"></textarea>
              </a-row>
              <a-row>
                <a-space>
                  <a-button @click="handleSendData"> 發送 </a-button>
                  <a-button @click="handleClearSendData"> 清空 </a-button>
                </a-space>
              </a-row>
            </div>
          </a-col>
          <a-col :span="6">
            <div>
              <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
                <a-form-item label="串口" v-bind="validateInfos.path">
                  <a-input v-model:value="portOptionsRef.path" />
                </a-form-item>
                <a-form-item label="波特率" v-bind="validateInfos.baudRate">
                  <a-input v-model:value="portOptionsRef.baudRate" />
                </a-form-item>
                <a-form-item :wrapper-col="{ span: 14, offset: 10 }">
                  <a-button type="primary" size="small" @click.prevent="handleOpenSerilPort">
                    打開串口
                  </a-button>
                  <a-button type="primary" size="small" danger @click="handleClosSerilPort"
                    >關閉串口</a-button
                  >
                </a-form-item>
              </a-form>
            </div>
          </a-col>
        </a-row>
      </div>
    </div>
  </PageWrapper>
</template>

<script lang="ts">
  import { defineComponent, reactive, toRaw, onMounted, toRefs, onUnmounted } from 'vue';
  import { PageWrapper } from '/@/components/Page';
  import { Form, Alert, Row, Col, message } from 'ant-design-vue';
  const { SerialPort } = require('serialport');
  // import { SerialPort } from 'serialport';

  const useForm = Form.useForm;

  export default defineComponent({
    components: {
      PageWrapper,
      'a-row': Row,
      'a-col': Col,
      'a-form': Form,
      'a-form-item': Form.Item,
      'a-alert': Alert,
    },
    setup() {
      let port;

      const state = reactive({
        errorMsg: '',
        message: '',
        toSendData: '',
        recievedData: '',
      });

      const portOptionsRef = reactive({
        path: 'COM1',
        baudRate: 9600, //波特率
        dataBits: 8, //數據位
        parity: 'none', //奇偶校驗
        stopBits: 1, //停止位
        flowControl: false,
        autoOpen: false, //不自動打開
      });

      const { validate, validateInfos } = useForm(
        portOptionsRef,
        reactive({
          path: [
            {
              required: true,
              message: '請輸入串口',
            },
          ],
          baudRate: [
            {
              required: true,
              message: '請輸入波特率',
            },
          ],
        }),
      );

      onMounted(() => {
        getSerialPortList();
        createeSerialPort();
      });

      const getSerialPortList = () => {
        SerialPort.list().then(
          (ports) => {
            if (!ports || ports.length == 0) {
              state.errorMsg = '未監測到串口信息';
            } else {
              // state.message = `監測到可用串口:${ports.map((e) => e.path)}`;
              ports.forEach(console.log);
            }
          },
          (err) => console.error(err),
        );
      };

      const createeSerialPort = () => {
        if (!port) {
          port = new SerialPort(toRaw(portOptionsRef));
          registerEvents();
        }
      };

      const handleOpenSerilPort = () => {
        validate()
          .then(() => {
            openSerialPort();
          })
          .catch((err) => {
            console.log('handleOpenSerilPort validate error', err);
          });
      };

      const openSerialPort = () => {
        const result = openPort();
        if (!result.success) {
          message.error(result.message);
        } else {
          message.success(result.message);
        }
      };

      const openPort = () => {
        const result = {
          success: true,
          message: '',
        };

        console.log('---------1-------------');
        console.log('openPort-port:', port);
        console.log('openPort-por1t.isOpen:', port?.isOpen);

        if (port == null) {
          console.log('---------2-------------');
          createeSerialPort();
        }
        console.log('---------3-------------');
        console.log('openPort-port:', port);
        console.log('openPort-port.isOpen:', port.isOpen);

        if (port.isOpen) {
          console.log('---------4-------------');
          result.message = `串口 ${portOptionsRef.path} 已打開成功`;
          return result;
        } else {
          port.open((err) => {
            console.assert('dddddd');
            console.log('---------5-------------');

            if (err) {
              console.log('---------7-------------');

              result.success = false;
              result.message = `串口 ${portOptionsRef.path} 打開失敗:${err}`;
              return result;
            } else {
              console.log('---------8-------------');
              result.message = `串口 ${portOptionsRef.path} 打開成功`;
              return result;
            }
          });
        }
        console.log('---------9-------------');
        return result;
      };

      const registerEvents = () => {
        // 監聽接收串口數據
        // Switches the port into "flowing mode"
        port.on('data', function (data) {
          console.log('RecievedData:', data);
          handleRecievedData(data);
        });
      };

      const handleRecievedData = (data) => {
        const str = Buffer.from(data).toString().toString();
        state.recievedData += str;
      };

      const handleClearRecievedData = () => {
        state.recievedData = '';
      };

      const handleClearSendData = () => {
        state.toSendData = '';
      };

      const handleSendData = () => {
        sendDateToPort(state.toSendData);
      };

      const sendDateToPort = (data) => {
        port.write(data);
        //port.write(Buffer.from(data));
      };

      const handleClosSerilPort = () => {
        const result = closeSerialPort();
        if (!result.success) {
          message.error(result.message);
        } else {
          message.success(result.message);
        }
      };

      const closeSerialPort = () => {
        const result = {
          success: true,
          message: '',
        };

        if (port && port.isOpen) {
          port.close((err) => {
            if (err) {
              result.success = false;
              result.message = `串口 ${portOptionsRef.path} 關閉失敗:${err}`;
              return result;
            }
          });
        }
        port = null;
        result.message = `串口 ${portOptionsRef.path} 已關閉`;
        return result;
      };

      onUnmounted(() => {
        closeSerialPort();
      });

      return {
        labelCol: {
          span: 10,
        },
        wrapperCol: {
          span: 14,
        },
        validateInfos,
        ...toRefs(state),
        portOptionsRef,
        handleOpenSerilPort,
        handleClosSerilPort,
        handleSendData,
        handleClearRecievedData,
        handleClearSendData,
      };
    },
  });
</script>

<style lang="less" scoped></style>

測試

運行開發環境

> pnpm dev:app

打開菜單:

Tips:
1.電腦沒有串口的,可以安裝虛擬串口軟件:Virtual Serial Port Driver
2.可以自行查找串口調試助手,比如 STC公司的:stc-isp

下面進行測試:

先使用 Virtual Serial Port Driver 添加了兩個虛擬串口

然後使用串口通訊助手測試數據通信,如下圖所示:

打包出現的問題

打包後運行程序,出現了錯誤,如下圖所示:

Tips:

按組合鍵 【ctrl + shift + i】打開開發者工具(Developer Tools)

錯誤信息如下:

vendor.b7ee0cad.js:147 Error: Cannot find module '@serialport/parser-byte-length'
Require stack:
- E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0\dist\win-unpacked\resources\app\node_modules\serialport\dist\index.js
...

index.ca01a1fc.js:1 Error: Cannot find module '@serialport/parser-byte-length'
Require stack:
- E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0\dist\win-unpacked\resources\app\node_modules\serialport\dist\index.js
- E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0\dist\win-unpacked\resources\app\dist\index.html
 ...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章