vben-admin 改造爲 electron 版本
vben-admin 簡介
目標
目標:將 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/
賬號密碼是預先好的,直接點擊登錄按鈕。
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/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
...