程序员的资源宝库

网站首页 > gitee 正文

vben-admin 改造为 electron 版本

sanyeah 2024-04-01 11:18:02 gitee 6 ℃ 0 评论

vben-admin 改造为 electron 版本

目录
  • vben-admin 简介
  • 目标
  • 其他方案
  • 下载 vben-admin 源码
  • 使用 pnpm 安装依赖
  • 运行
  • electron 版本
    • 使用 pnpm 安装依赖
      • devDependencies
      • dependencies
    • 使用 yarn 安装
      • devDependencies
      • dependencies
    • 集成electron代码
      • index.ts
      • rollupElectronConfig.ts
      • compilerElectron.ts
      • startElectron.ts
    • 命令
    • 运行 electron
    • 打包
      • 出现错误 : 找不到文件
  • electron 示例
    • 进程间通信
      • 渲染进程
      • 主进程
      • 添加路由菜单
      • 国际化
      • 测试
  • 集成串口
    • 安装
      • 可能出现的问题
    • 国际化
    • 添加路由菜单
    • 串口的使用示例
    • 测试
    • 打包出现的问题
      • serialport 打包问题最终解决方法
  • 集成 SQLite
    • 安装 sqlite3
      • 不要使用 pnpm
    • kenx.js
    • 相关代码
      • 创建数据库
      • 渲染进程中使用 kenx
      • 添加路由
      • 国际化
    • 开发环境运行
      • 出现的错误提示
    • 打包
      • 绿色版没问题
      • 安装版的报错
        • 打包时复制数据库文件
        • 提升权限

vben-admin 简介

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

目标

目标:授之以鱼,不如授之以渔,本文旨在教会大家自己动手改造 vben-admin 改造为 electron 版本。
下面是改造后的 electron 版本源码地址,供参考:
https://gitee.com/Artisan-k/vben-admin-electron

其他方案

vite-plugin-electron:https://github.com/electron-vite/vite-plugin-electron

下载 vben-admin 源码

完整版:

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

使用 pnpm 安装依赖

下载后运行:

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

下载完,有一个提示错误:

> vben-admin@2.7.2 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


> vben-admin@2.7.2 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 版本

使用 pnpm 安装依赖

推荐使用 yarn,但是为了亲自体验使用 pnpm 出现的问题,这里还是使用 pnpm

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

使用 yarn 安装

devDependencies

yarn add electron electron-builder electron-connect electron-contextmenu-middleware electron-input-menu wait-on -D

dependencies

yarn add electron-is-dev rollup rollup-plugin-esbuild @rollup/plugin-alias @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-json @rollup/plugin-node-resolve esbuild chalk ora -S

集成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": "xxx@gmail.com",
    "electronDownload": {
      "mirror": "https://npm.taobao.org/mirrors/electron/"
    },
    "files": [
      "!node_modules",
      "dist/**",
    ],
    "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 serialport@9.2.8 
pnpm install electron-rebuild
.\node_modules\.bin\electron-rebuild.cmd 

serialport@9.x 支持 windows 7、8、10+

serialport@10.x 支持 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>

注意:这里使用的是

  const { SerialPort } = require('serialport');

而不是

 import { SerialPort } from 'serialport';

测试

运行开发环境

> 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
 ...

serialport 打包问题最终解决方法

最终的解决方案是:

package.json 文件中的如下配置:

... 
"build": {
    ...
    "files": [
      "!node_modules",
      "dist/**"
    ],
    .... 
 }
 ...

中的 "!node_modules", 删除掉,即最终的配置为:

... 
"build": {
    ...
    "files": [
      "dist/**"
    ],
    .... 
 }
 ...

使用 pnpm 进行打包

pnpm build:app

不幸的是,使用 pnpm 会出现如下错误:

> pnpm build:app
[compiler-electron] Electron build successed.
  ? electron-builder  version=23.1.0 os=10.0.19044
  ? loaded configuration  file=package.json ("build" field)
  ? description is missed in the package.json  appPackageFile=E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0\package.json
  ? writing effective config  file=dist\builder-effective-config.yaml
  ? rebuilding native dependencies  dependencies=@serialport/bindings-cpp@10.7.0 platform=win32 arch=x64
  ? cannot execute  cause=fork/exec C:\Users\wei\AppData\Roaming\npm\node_modules\pnpm\bin\pnpm.cjs: %1 is not a valid Win32 application.
                    command='C:\Users\wei\AppData\Roaming\npm\node_modules\pnpm\bin\pnpm.cjs' rebuild @serialport/bindings-cpp@10.7.0
                    workingDir=
?ELIFECYCLE? Command failed with exit code 1.

更换 pnpmyarn

  • 删除掉 node_modules 文件夹

  • 删除文件:pnpm-lock.yaml

  • 安装依赖

    yarn install
    
  • 打包

    yarn build:app
    

这样执行打包后,打包文件把 node_modules也打包到如下文件夹:

dist\win-unpacked\resources\app\node_modules

如下图所示:


点击安装文件【vben-admin_setup_2.8.0.exe】,进行安装,安装后的如下目录中,可以看到被打包的 node_modules 文件夹

安装文件也变大了,但是这是目前能找到的唯一解决方案。

TODO:看看怎么优化下,只导入 serialprot及其依赖

集成 SQLite

https://blog.csdn.net/jiyulonely/article/details/77089701

菜鸟教程:https://www.runoob.com/sqlite/sqlite-where-clause.html

安装 sqlite3

yarn add sqlite3

不要使用 pnpm

使用 pnpm 安装 sqlite3

pnpm install sqlite3 --save
pnpm install electron-rebuild --save
.\node_modules\.bin\electron-rebuild.cmd

或者

> npm install sqlite3 --build-from-source --runtime=electron --target=17.1.0 --disturl=https://atom.io/download/electron   
  • --target=17.1.0: 是项目中使用的 electron 版本

使用 pnpm 安装 sqlite3,会出现各种各样的问题,比如如下错误:

> pnpm install
? Building module: sqlite3, Completed: 1在此解决方案中一次生成一个项目。若要启用并行生成,请添加“-m”开关。
? Building module: sqlite3, Completed: 1C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Microsoft\VC\v170\Microsoft.CppBuild.targets(989,5): error MSB3191: 无法创建目录“E:\artisan\labs\antdv-pr
o\src\electron\node_modules\.pnpm\registry.npmmirror.com+sqlite3@5.0.2\registry.npmmirror.com+node-addon-api@3.2.1\node_modules\node-addon-api\Release\obj\nothing\reg 
istry.npmmirror.com+node-addon-api@3.2.1\node_modules\node-addon-api”。指定的路径或文件名太长,或者两者都太长。完全限定文件名必须少于 260 个字符,并且目录名必须少于 248 个字符。 [E:\artisan\labs\antdv-pro\src\electr
......

npmyarn 命令安装,不用 electron-rebuild

npm install sqlite3 --build-from-source --runtime=electron --target=13.0.0 --dist-url=https://atom.io/download/electron
或
yarn add sqlite3 --build-from-source --runtime=electron --target=13.0.0 --dist-url=https://atom.io/download/electron

在vben-admin中,直接使用如下命令安装即可:

yarn add sqlite3

electron-builder 会自动帮我们编译出对应 electron 版本的 sqlite3

kenx.js

https://knexjs.org/

https://github.com/knex/knex

knex js 是一个sql 指令构建器,可以操作 sqlite、mysql等

安装 kenx.js

yarn add knex -S

knex.js 链接 sqlite3时,还得安装 @vscode/sqlite3

yarn add @vscode/sqlite3 

相关代码

创建数据库

代码清单:electron-main/common/database-sqlite.ts

require('sqlite3'); //确保knex使用sqlite3时它已加载
console.log('------1--------');

const knex = require('knex');

console.log('------2--------');
const database = knex({
  client: 'sqlite3',
  connection: {
    filename: './vben-admin-db.sqlite',
  },
  useNullAsDefault: true,
});
console.log('------3--------');

// 检查数据库是否存在数据库表,不存在则创建
database.schema.hasTable('users').then((exists) => {
  console.log('------4--------', `!exists: ${!exists}`);
  if (!exists) {
    console.log('------5--------');
    return database.schema.createTable('users', (t) => {
      t.increments('id').primary(); //创建自增的id列,并设置为主键
      t.string('name', 100);
      t.boolean('isActived');
    });
  }
});
console.log('------6--------');

export default database;

渲染进程中使用 kenx

代码清单:src/views/electron/sqlite/SqliteKenx.vue

<template>
  <PageWrapper
    title="示例:SQLite & Knex.js"
    contentBackground
    contentClass="p-4"
    content="在这里将演示如何在Electron中使用 SQLite & Knex.js, 官网:https://knexjs.org"
  >
    <div>
      <div style="margin-top: 10px">
        <h2>Users</h2>
        <a-button @click="hanldeAddUser">添加</a-button>
      </div>
      <div>序号| Id | name | isActived</div>
      <div v-for="item in users" :key="item.id">
        {{ item.id }} | {{ item.name }} | {{ item.isActived }} |
        <a-button @click="hanldeDelUser(item.id)">删除</a-button>
      </div>
    </div>
  </PageWrapper>
</template>

<script lang="ts">
  import { defineComponent, reactive, toRefs, onMounted } from 'vue';
  import { PageWrapper } from '/@/components/Page';
  import { User } from './types';
  import database from '../../../../electron-main/common/database-sqlite';

  export default defineComponent({
    components: {
      PageWrapper,
    },
    setup() {
      const state = reactive({
        users: new Array<User>(),
      });
      onMounted(() => {
        getUserList();
      });

      const getUserList = () => {
        database('users')
          .select()
          .orderBy('id', 'desc')
          .then((items) => {
            console.log('users:', items);
            state.users = items;
          })
          .catch(console.error);
      };

      const hanldeAddUser = () => {
        const user = {
          name: 'users' + Math.random() * 10000,
          isActived: false,
        };

        database('users')
          .insert(user)
          .then(() => {
            getUserList();
          })
          .catch(console.error);
      };

      const hanldeDelUser = (id) => {
        console.log('id:', id);
        database('users')
          .where('id', id)
          .delete()
          .then(() => {
            getUserList();
          })
          .catch(console.error);
      };

      return {
        ...toRefs(state),
        hanldeAddUser,
        hanldeDelUser,
      };
    },
  });
</script>

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

代码清单:src/views/electron/sqlite/types.ts

export interface User {
  id: number;
  name: string;
  isActived: boolean;
}

添加路由

代码清单:src/router/routes/modules/elctron.ts

const electron: AppRouteModule = {
  path: '/electron',
  name: 'Electron',
    ...
  },
  children: [
    ...
    {
      path: 'Sqlite',
      name: 'Sqlite',
      component: getParentLayout('Sqlite'),
      meta: {
        title: t('routes.electron.Sqlite'),
      },
      redirect: '/electron/Sqlite/sqlite-kenx',
      children: [
        {
          path: 'sqlite-kenx',
          name: 'SqliteKenx',
          meta: {
            title: t('routes.electron.SqliteKenx'),
          },
          component: () => import('/@/views/electron/sqlite/SqliteKenx.vue'),
        },
      ],
    },
  ]
}

国际化

路由菜单支持国际化,需要新建两个多语言资源,新建如下两个文件,新建后系统会自动加载,可以直接使用。

  • 英文

代码清单:src/locales/lang/en/routes/electron.ts

export default {
  ...
   Sqlite: 'Sqlite',
  SqliteKenx: 'Sqlite-Kenx',
};
  • 简体中文

代码清单:src/locales/lang/zh-CN/routes/electron.ts

export default {
  ...
   Sqlite: 'Sqlite',
  SqliteKenx: 'Sqlite-Kenx',
};

开发环境运行

yarn dev:app

程序启动后,点击菜单【Sqlite | Sqlite-Kenx】,如下图所示:

第一次运行点击菜单时,数据库还没有生成,这时会在项目的根目录下生成一个名为【vben-admin-db.sqlite】的sqlite数据库,如下图所示:

点击【添加】按钮,添加一个 User,

点击 【删除】删除 User

出现的错误提示

出现如下错误提示,但是程序还是可以运行,

X [ERROR] Could not resolve "mock-aws-s3"

    node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:43:28:
      43 │     const AWSMock = require('mock-aws-s3');
         ?                             ~~~~~~~~~~~~~

  You can mark the path "mock-aws-s3" as external to exclude it from the bundle, which will remove
  this error. You can also surround this "require" call with a try/catch block to handle this
  failure at run-time instead of bundle-time.

X [ERROR] Could not resolve "aws-sdk"

    node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:76:22:
      76 │   const AWS = require('aws-sdk');
         ?                       ~~~~~~~~~

  You can mark the path "aws-sdk" as external to exclude it from the bundle, which will remove this
  error. You can also surround this "require" call with a try/catch block to handle this failure at
  run-time instead of bundle-time.

X [ERROR] Could not resolve "nock"

    node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:112:23:
      112 │   const nock = require('nock');
          ?                        ~~~~~~

  You can mark the path "nock" as external to exclude it from the bundle, which will remove this
  error. You can also surround this "require" call with a try/catch block to handle this failure at
  run-time instead of bundle-time.
  
20:17:55 [vite] error while updating dependencies:
Error: Build failed with 3 errors:
node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:43:28: ERROR: Could not resolve "mock-aws-s3"
node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:76:22: ERROR: Could not resolve "aws-sdk"
node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:112:23: ERROR: Could not resolve "nock"
    at failureErrorWithLog (E:\artisan\labs\vben-admin-electron\src\vue-vben-admin-2.8.0\node_modules\esbuild\lib\main.js:1605:15)
    ...
Vite Error, /node_modules/.vite/deps/vue.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/pinia.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/vue-router.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/ant-design-vue.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/vue-i18n.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/vue.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/ant-design-vue.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/lodash-es.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/vite-plugin-theme_es_client.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/axios.js?v=db3ca739 optimized info should be defined
Vite Error, /node_modules/.vite/deps/vite-plugin-theme_es_client.js?v=db3ca739 optimized info should be defin

....

根据报错提示,

 Could not resolve "mock-aws-s3"...
  You can mark the path "mock-aws-s3" as external
 ...
 Could not resolve "aws-sdk...
  You can mark the path "aws-sdk" as external
 ... 
 Could not resolve "nock" ...
  You can mark the path "nock" as external

安装 Vite 插件: vite-plugin-resolve-externals

yarn add vite-plugin-resolve-externals -D

然后创建插件:

代码清单: build/vite/plugin/externals.ts

import resolveExternalsPlugin from 'vite-plugin-resolve-externals';

export function configResolveExternalsPlugin() {
  return resolveExternalsPlugin({
    'mock-aws-s3': 'mock-aws-s3',
    'aws-sdk': 'aws-sdk',
    nock: 'nock',
  });
}

把无法 resolve 的模块配置进去,最后把生成的插件对象添加到 vite.config.ts 的插件配置节点,

代码清单:vite.config.ts

import { createVitePlugins } from './build/vite/plugin';
...
export default ({ command, mode }: ConfigEnv): UserConfig => {
    ...
    // The vite plugin used by the project. The quantity is large, so it is separately extracted and managed
    plugins: createVitePlugins(viteEnv, isBuild),
}

代码清单:build/vite/plugin/index.ts

import { configResolveExternalsPlugin } from './externals';
...
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
   const vitePlugins: (Plugin | Plugin[])[] = [...];
   ...
   vitePlugins.push(configResolveExternalsPlugin());

   return vitePlugins;
}

其中, vitePlugins.push(configResolveExternalsPlugin()); 把刚才创建的插件添加到 vite.config.ts 中。

打包

yarn build:app

也是需要把 package.json 文件

  "build": {
    "appId": "xxx@gmail.com",
    ...
    "files": [
      "!node_modules",
      "dist/**"
    ],
    ...

中的 "!node_modules" 删掉,即最终配置为:

  "build": {
    "appId": "xxx@gmail.com",
    ...
    "files": [
      "dist/**"
    ],
    ...

否则,报错,如下图所示:

index.4583ee62.js:117 Error: Cannot find module 'sqlite3'

绿色版没问题

打包成功后,打开程序的绿色版,第一次运行点击菜单时,数据库还没有生成,这时会在程序的根目录下生成一个名为【vben-admin-db.sqlite】的sqlite数据库,如下图所示:

点击【添加】按钮,添加一个 User,

点击 【删除】删除 User

安装版的报错

在目录中dist 双击安装文件 vben-admin_setup_2.8.0.exe 进行安装,安装完成后,打开应用程序,会报如下错误:

错误信息如下:

Error: SQLITE_CANTOPEN: unable to open database file

没有找到数据库文件。

打包时复制数据库文件

这时需要把数据库也打包进去,修改 package.json

"build": {
    "appId": "vben-admin@gmail.com",
    ...
    "extraFiles":["./vben-admin-db.sqlite"],
    ...

Tips:

extraFiles 的配置项见:

https://www.electron.build/configuration/contents.html#extrafiles

这样配置后,打包时,会把数据库文件 vben-admin-db.sqlite 复制到程序的目录下。

然后进行打包,点安装文件进行安装,

从安装目录中,可以看到数据库文件 vben-admin-db.sqlite 已经复制到程序的根目录下,如下图所示:

打开程序,读取数据库没有问题,但是如果对数据库进行新增,删除操作又报错,

attempt to write a readonly database

如下图所示:

可以修改数据库文件的属性为可以:写入,修改,如下图所示:

但是让用户安装后去修改这个显然是不可取的。

提升权限

这时需要提升权限,修改 package.json

"build": {
    "appId": "vben-admin@gmail.com",
    ...
    "extraFiles":["./vben-admin-db.sqlite"],
    ...
    "nsis": {
      ...
      "perMachine": false,
      "allowElevation": true, 
       ...
    }

Tips:

allowElevation 的配置项,参见:https://www.electron.build/generated/nsisoptions

  • oneClick = true Boolean - Whether to create one-click installer or assisted.

  • perMachine = false Boolean - Whether to show install mode installer page (choice per-machine or per-user) for assisted installer. Or whether installation always per all users (per-machine).

    If oneClick is true (default): Whether to install per all users (per-machine).

    If oneClick is false and perMachine is true: no install mode installer page, always install per-machine.

    If oneClick is false and perMachine is false (default): install mode installer page.

  • allowElevation = true Boolean - assisted installer only. Allow requesting for elevation. If false, user will have to restart installer with elevated permissions.

  • allowToChangeInstallationDirectory = false Boolean - assisted installer only. Whether to allow user to change installation directory.

重新打包,然后进行安装,这时安装界面如下:

点下一步,请一定使用它设置的默认安装目录

C:\Users\wei\AppData\Local\Programs\vben-admin

如下图所示:

安装成功后,这时不会再提示错误了。

特别注意

当设置 "allowElevation"的值为 true 时,如果自定义安装目录是无法安装成功的,如下图所示,

然后安装提示错误:

所以这时可以禁止用户选择安装路径,修改 package.json 文件:

    ...
    "nsis": {
       ...
      "allowToChangeInstallationDirectory": false,
    }

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表