ruoyi&vue+electron+ffi&dll+CAN+串口

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "test",
  "author": "tzc",
  "license": "MIT",
  "main": "background.js",
  "scripts": {
    "dev": "vue-cli-service serve",
    "build:prod": "vue-cli-service build",
    "build:stage": "vue-cli-service build --mode staging",
    "build:dev": "vue-cli-service build --mode development",
    "preview": "node build/index.js --preview",
    "lint": "eslint --ext .js,.vue src",
    "electron:serve": "vue-cli-service electron .",
    "electron:build": "vue-cli-service electron:build",
    "electron:build32": "vue-cli-service electron:build --win --ia32"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "src/**/*.{js,vue}": [
      "eslint --fix",
      "git add"
    ]
  },
  "keywords": [
    "vue",
    "admin",
    "dashboard",
    "element-ui",
    "boilerplate",
    "admin-template",
    "management-system"
  ],
  "repository": {
    "type": "git",
    "url": "https://gitee.com/dromara/Vue-Plus.git"
  },
  "dependencies": {
    "@riophae/vue-treeselect": "0.4.0",
    "axios": "0.24.0",
    "clipboard": "2.0.8",
    "core-js": "3.25.3",
    "echarts": "5.4.0",
    "electron": "20.3.8",
    "electron-devtools-installer": "^3.2.0",
    "electron-log": "^4.0.0",
    "electron-store": "^8.1.0",
    "element-ui": "2.15.13",
    "ffi-napi": "^4.0.3",
    "file-saver": "2.0.5",
    "fuse.js": "6.4.3",
    "highlight.js": "9.18.5",
    "js-beautify": "1.13.0",
    "js-cookie": "3.0.1",
    "jsencrypt": "3.0.0-rc.1",
    "nprogress": "0.2.0",
    "quill": "1.3.7",
    "ref-array-di": "^1.2.2",
    "ref-array-napi": "^1.2.2",
    "ref-napi": "^3.0.3",
    "ref-struct-di": "^1.1.1",
    "ref-struct-napi": "^1.1.1",
    "screenfull": "5.0.2",
    "serialport": "^12.0.0",
    "sortablejs": "1.10.2",
    "vue": "2.6.12",
    "vue-cli-plugin-electron-builder": "^2.1.1",
    "vue-count-to": "1.0.13",
    "vue-cropper": "0.5.5",
    "vue-meta": "2.4.0",
    "vue-router": "3.4.9",
    "vuedraggable": "2.24.3",
    "vuex": "3.6.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "4.4.6",
    "@vue/cli-plugin-eslint": "4.4.6",
    "@vue/cli-service": "4.4.6",
    "babel-eslint": "10.1.0",
    "babel-plugin-dynamic-import-node": "2.3.3",
    "chalk": "4.1.0",
    "compression-webpack-plugin": "5.0.2",
    "connect": "3.6.6",
    "eslint": "7.15.0",
    "eslint-plugin-vue": "7.2.0",
    "lint-staged": "10.5.3",
    "runjs": "4.4.2",
    "sass": "1.32.13",
    "sass-loader": "10.1.1",
    "script-ext-html-webpack-plugin": "2.1.5",
    "svg-sprite-loader": "5.1.1",
    "vue-template-compiler": "2.6.12"
  },
  "engines": {
    "node": ">=8.9",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

.env.production

VUE_APP_BASE_API = 'http://localhost:8080'
VUE_APP_CONTEXT_PATH = './'

src/directive/module/clipboard.js

整个文件全部注释掉

vue.config.js

pluginOptions是module.exports下最顶级的属性:

pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      //因为这两个模块中包含原生 C代码,所以要在运行的时候再获取,而不是被webpack打包到bundle中
      externals: ['ffi-napi', 'ref-napi','ref-struct-di','ref-array-di','serialport'],
      contextIsolation: false,
      enableRemoteModule: true,
      publish: [{
        "provider": "xx公司",
        "url": "https://www.xxx.cn/"
      }],
      "copyright": "Copyright © 2023",
      builderOptions:{
        appId: 'com.xxx',
        productName: 'Xxx',
        asar:false,
        "extraResources": [
          {
            "from": "./src/usb/exe/ControlCAN64.dll",
            "to": "./usb/exe/ControlCAN64.dll"
          },
          {
            "from": "./src/usb/conf/data_init.json",
            "to": "./usb/conf/data_init.json"
          },
          {
            "from": "./src/usb/conf/data_zh_cn.json",
            "to": "./usb/conf/data_zh_cn.json"
          },
        ],
        nsis:{
          "oneClick": false,
          "guid": "idea",
          "perMachine": true,
          "allowElevation": true,
          "allowToChangeInstallationDirectory": true,
          "installerIcon": "build/app.ico",
          "uninstallerIcon": "build/app.ico",
          "installerHeaderIcon": "build/app.ico",
          "createDesktopShortcut": true,
          "createStartMenuShortcut": true,
          "shortcutName": "Xxx"
        },
        win: {
          "icon": "build/app.ico",
          "target": [
            {
              "target": "nsis",			//使用nsis打成安装包,"portable"打包成免安装版
              "arch": [
                "ia32",				//32位
                "x64" 				//64位
              ]
            }
          ]
        },
      },
    },
  },

全局

Cookies

Cookies.get替换为localStorage.getItem
Cookies.set替换为localStorage.setItem
Cookies.remove替换为localStorage.removeItem
src/views/login.vue 去掉过期时间

path.resolve

全局修改path.resolve为path.posix.resolve

src/router/index.js

改为 hash 模式

新增src/background.js

'use strict'
 
import { app, protocol, BrowserWindow, ipcMain } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
const Store = require('electron-store');
 
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])
 
async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      contextIsolation:false,     //上下文隔离
      enableRemoteModule: true,   //启用远程模块
      nodeIntegration: true, //开启自带node环境
      webviewTag: true,     //开启webview
      webSecurity: false,
      allowDisplayingInsecureContent: true,
      allowRunningInsecureContent: true
    }
  })
  win.maximize()
  win.show()
  win.webContents.openDevTools()

  ipcMain.on('getUsbConnectionStatus', (event, arg) => {
    global.usbConnectionStatus=new Date().getTime()-global.usbConnectionTime<5000?true:false;//5秒内有数据表示连接成功
    event.returnValue = global.usbConnectionStatus;
  });

  ipcMain.on('openUsb', (event) => {
  	let res=UsbCanUtils.openUsb();
  	event.returnValue = res;
  });
 
  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
 
 
  }
}
 
// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
 
app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
  Store.initRenderer();
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
    try {
      await installExtension(VUEJS_DEVTOOLS)
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  }
  createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

vue页面调用electron

  ipcMain.on('getUsbConnectionStatus', (event, arg) => {
    global.usbConnectionStatus=new Date().getTime()-global.usbConnectionTime<5000?true:false;//5秒内有数据表示连接成功
    event.returnValue = global.usbConnectionStatus;
  });

  ipcMain.on('openUsb', (event) => {
  	let res=UsbCanUtils.openUsb();
  	event.returnValue = res;
  });
  methods: {
    getUsbConnectionStatus: function() {
      const result = ipcRenderer.sendSync('getUsbConnectionStatus', '');
      console.log("连接状态:"+result);
      this.usbConnectionStatus=result;
    },
    openUsb: function() {
      if(this.usbConnectionStatus){
        return;
      }
      ipcRenderer.send('openUsb', '');
    }
  }

electron调用CAN、dll

UsbCanTypes.js

const ffi = require("ffi-napi");
const ref = require("ref-napi");
const Struct = require('ref-struct-di')(ref);
const ArrayType = require('ref-array-di')(ref);

var VCI_INIT_CONFIG = Struct({
    AccCode:    ref.types.uint32,
    AccMask:    ref.types.uint32,
    Reserved:   ref.types.uint32,
    Filter: ref.types.uchar,
    Timing0:    ref.types.uchar,
    Timing1:    ref.types.uchar,
    Mode:   ref.types.uchar
})
var VCI_CAN_OBJ = Struct({
    ID:    ref.types.uint32,
    TimeStamp:    ref.types.uint32,
    TimeFlag:   ref.types.byte,
    SendType: ref.types.byte,
    RemoteFlag:    ref.types.byte,
    ExternFlag:    ref.types.byte,
    DataLen:   ref.types.byte,
    Data:   ArrayType(ref.types.byte, 8),
    Reserved:   ArrayType(ref.types.byte, 3)
})

let StructArray = ArrayType(VCI_CAN_OBJ);

export default {
    VCI_INIT_CONFIG,
    VCI_CAN_OBJ,
    StructArray,
}

UsbCanConfigs.js

import path from 'path';
import UsbCanTypes from './UsbCanTypes';

const VCI_USBCAN2=4
const canIndex=0
const SendType=0
const ExternFlag=1

let vci_initconfig = new UsbCanTypes.VCI_INIT_CONFIG({
    AccCode:    0x80000008,
    AccMask:    0xFFFFFFFF,
    Reserved:   0,
    Filter: 0,
    Timing0:    0x01,
    Timing1:    0x1C,
    Mode:   0
})//波特率250k,正常模式

export default {
    VCI_USBCAN2,
    vci_initconfig,
    SendType,
    ExternFlag,
    canIndex,
}

UsbCanUtils.js

const path = require('path')

const ffi = require("ffi-napi");
const ref = require("ref-napi");

const log = typeof process !== 'undefined' && process.versions && process.versions.electron?require('electron-log'):console;

import CommUtils from './CommUtils';
import UsbCanTypes from './UsbCanTypes';
import UsbCanConfigs from './UsbCanConfigs';
import UsbCanProtocol from './UsbCanProtocol';
import { setInterval, setTimeout } from 'core-js';
// const UsbCanTypes = require('./UsbCanTypes.js');
// const UsbCanConfigs = require('./UsbCanConfigs');
// const UsbCanProtocol = require('./UsbCanProtocol.js');

const STATUS_OK = 1

let canDLL = null;

function create() {
    if(canDLL!=null){
        return
    }
    canDLL = ffi.Library(path.join(process.cwd(), '/resources/usb/exe/ControlCAN64.dll'), {
        'VCI_OpenDevice': [ref.types.int, [ref.types.int, ref.types.int, ref.types.int]],
        'VCI_InitCAN': [ref.types.int, [ref.types.int, ref.types.int, ref.types.int,ref.refType('void')]],
        'VCI_StartCAN': [ref.types.int, [ref.types.int, ref.types.int, ref.types.int]],
        'VCI_Transmit': [ref.types.int, [ref.types.int, ref.types.int, ref.types.int,ref.refType('void'), ref.types.int]],
        'VCI_Receive': [ref.types.int, [ref.types.int, ref.types.int, ref.types.int,ref.refType('void'),ref.types.ulong,ref.types.int]]
    });
}

function open() {
    let ret = canDLL.VCI_OpenDevice(UsbCanConfigs.VCI_USBCAN2,0,0)
    return ret== STATUS_OK
}

function init(canIndex) {
    let ret = canDLL.VCI_InitCAN(UsbCanConfigs.VCI_USBCAN2, 0, canIndex, UsbCanConfigs.vci_initconfig.ref())
    return ret== STATUS_OK
}

function start(canIndex) {
    let ret = canDLL.VCI_StartCAN(UsbCanConfigs.VCI_USBCAN2, 0, canIndex)
    return ret== STATUS_OK
}
function send(canIndex,id,dataLen,data) {
    //log.info('CAN'+canIndex+'通道准备发送')
    let d={
        ID:    id,
        TimeStamp:    0,
        TimeFlag:   0,
        SendType: UsbCanConfigs.SendType,
        RemoteFlag:    0,
        ExternFlag:    UsbCanConfigs.ExternFlag,
        DataLen:   dataLen,
        Data:   data,
        Reserved:   [0,0,0]
    };
    let vci_can_obj = new UsbCanTypes.VCI_CAN_OBJ();
    vci_can_obj.ID=d.ID;
    vci_can_obj.TimeStamp=d.TimeStamp;
    vci_can_obj.TimeFlag=d.TimeFlag;
    vci_can_obj.SendType=d.SendType;
    vci_can_obj.RemoteFlag=d.RemoteFlag;
    vci_can_obj.ExternFlag=d.ExternFlag;
    vci_can_obj.DataLen=d.DataLen;
    for(let i=0;i<dataLen;i++){
        vci_can_obj.Data[i]=d.Data[i]
    }
    for(let i=0;i<3;i++){
        vci_can_obj.Reserved[i]=d.Reserved[i]
    }

    let ret = canDLL.VCI_Transmit(UsbCanConfigs.VCI_USBCAN2, 0, canIndex, vci_can_obj.ref(), 1)
    //log.info('CAN'+canIndex+'通道发送完成'+ret)
    return ret== STATUS_OK
}

function receive(canIndex) {
    const rx_vci_can_obj = new UsbCanTypes.StructArray(2500);
    let usbInterval=setInterval(() => {
        if(global.windowClosed){
            return;
        }
        let ret = canDLL.VCI_Receive(UsbCanConfigs.VCI_USBCAN2, 0, canIndex, rx_vci_can_obj.buffer, 2500, 0)
        // //log.info('CAN1通道接收'+ret)
        if (ret > 0){//接收到数据
            global.usbConnectionTime=new Date().getTime();
            for(let i=0;i<ret;i++){
                // //log.info('ID:')
                // //log.info(CommUtils.toHexString(rx_vci_can_obj[i].ID))
                // //log.info('DataLen:')
                // //log.info(CommUtils.toHexString(rx_vci_can_obj[i].DataLen))
                // //log.info('Data:')
                // //log.info(CommUtils.listToHexString(rx_vci_can_obj[i].Data))
                UsbCanProtocol.parseData(canIndex,rx_vci_can_obj[i].ID,rx_vci_can_obj[i].DataLen,rx_vci_can_obj[i].Data.toArray())
            }
        }
    }, 10);
    return usbInterval;
    // while(true){//一直循环查询接收。
    //     let ret = canDLL.VCI_Receive(UsbCanConfigs.VCI_USBCAN2, 0, canIndex, rx_vci_can_obj.buffer, 2500, 0)
    //     //log.info('CAN1通道接收'+ret)
    //     if (ret > 0){//接收到数据
    //         global.usbConnectionTime=new Date().getTime();
    //         for(let i=0;i
    //             // //log.info('CAN1通道接收成功')
    //             // //log.info('ID:')
    //             // //log.info(CommUtils.toHexString(rx_vci_can_obj[i].ID))
    //             // //log.info('DataLen:')
    //             // //log.info(CommUtils.toHexString(rx_vci_can_obj[i].DataLen))
    //             // //log.info('Data:')
    //             // //log.info(CommUtils.listToHexString(rx_vci_can_obj[i].Data))
    //             // //log.info('\r\n')
    //             UsbCanProtocol.parseData(canIndex,rx_vci_can_obj[i].ID,rx_vci_can_obj[i].DataLen,rx_vci_can_obj[i].Data.toArray())
    //         }
    //     }
    // }
}

export default {
    openUsb:function(){
        global.usbConnectionTime=0;
        //log.info("create USB ing...")
        create();
        //log.info("create USB success")
        let res=open();
        if(res){
            //log.info("open USB success")
        }else{
            //log.info("open USB fail")

            return false;
        }
        res=init(UsbCanConfigs.canIndex);
        if(res){
            //log.info("init USB success")
        }else{
            //log.info("init USB fail")
            return false;
        }
        res=start(UsbCanConfigs.canIndex);
        if(res){
            //log.info("start USB success")
            global.usbConnectionTime=new Date().getTime();
        }else{
            //log.info("start USB fail")
            return false;
        }
        return true;
    },
    send:send,
    receive:function(){
        setTimeout(function(){
            global.usbInterval=receive(UsbCanConfigs.canIndex);
        },10);
    },
}

electron调用串口

SerialUtils.js

const {SerialPort} = require('serialport');
// const Readline = require('@serialport/parser-readline');
import CommUtils from './CommUtils';

const log = typeof process !== 'undefined' && process.versions && process.versions.electron?require('electron-log'):console;

let port=null;
let parser = null;

async function getPortList() {
    try {
        // 获取可用串口列表,并等待Promise解析
        const ports = await SerialPort.list();
        // ports 是一个包含可用串口信息的数组
        // log.info('Available Serial Ports:', (typeof ports));
        log.info('Available Serial Ports:', ports);
        // 返回解析后的串口列表
        return ports;
    } catch (e) {
        // 处理错误
        log.error(`Error while getting serial port list:${e}>>>${e.stack}`);
        throw e; // 将错误继续抛出,让调用者处理
    }
}

function open(serialName,baudRate) {
    try {
        port = new SerialPort({
            path: serialName,
            baudRate: baudRate,
            // highWaterMark:32,
            autoOpen: true,
            dataBits: 8,   // 8位数据位
            parity: 'none', // 无奇偶校验
            stopBits: 1,    // 1个停止位
            flowControl: false,
            platformOptions: {
                vmin: 1, // 读取的最小字节数
                vtime: 1, // 读取操作的超时时间(以十毫秒为单位)
                ctsFlowControl: false, // 是否启用 CTS 流控制
                autoOpen: true // 是否自动打开串口
            }
        });
        port.on('error', err => {
            console.log(err);
        });
        return true;
    } catch (e) {
        // log.error(`打开串口报错:${e}>>>${e.stack}`);
        return false;
    }
}
function close() {
    try {
        port.close();
        global.usbConnectionTime=0;
        global.usbConnectionStatus=false;
        setTimeout(() => {
            global.usbConnectionTime=0;
            global.usbConnectionStatus=false;
        },100);
        return true;
    } catch (e) {
        // log.error(`关闭串口报错:${e}>>>${e.stack}`);
        return false;
    }
}

function start() {
    // try {
    //     // 使用 Readline parser 以行为单位读取数据
    //     parser = port.pipe(new Readline({ delimiter: '\n' }));
    //     return true;
    // } catch (e) {
    //     log.error(`初始化串口报错:${e}>>>${e.stack}`);
    //     return false;
    // }
    return true;
}

function mysend(data) {
    port.write(Buffer.from(data), (e,r) => {
        if(e){
            log.error("New发送串口数据>>>"+CommUtils.listToHexString(data))
        }else{
            // log.info("New发送串口数据>>>"+CommUtils.listToHexString(data))
        }
    });
}

//发送并返回发送的结果
function send(data) {
    try {
        mysend(data);
        // log.info(`发送串口数据`+CommUtils.listToHexString(data));
        return true;
    } catch (e) {
        // log.error(`发送数据给串口报错:`+CommUtils.listToHexString(data)+`>>>${e.stack}`);
        return false;
    }
}

function receive(callback) {
    try {
        // 监听串口数据接收事件
        port.on('data', (data) => {
            // let x1=data.indexOf(0x2A);
            // let x2=data.indexOf(0x04);
            // if(x1!=-1&&x2!=-1&&x1==x2-1){
            //   log.info("收到串口数据"+CommUtils.listToHexString(data))
            // }
            try {
                log.info("New收到串口数据>>>"+CommUtils.listToHexString(data))
                global.usbConnectionTime=new Date().getTime();
                callback(data);
            } catch (e2) {
                log.error(`接收数据串口报错2:${e2.stack}`);
            }
        });
    } catch (e) {
        log.error(`接收数据串口报错:${e.stack}`);
    }
}


export default {
    openSerial:function(serialName){
        //460800
        //921600
        let res=open(serialName,460800);
        if(res){
            log.info("open Serial success")
        }else{
            log.info("open Serial fail")
            return false;
        }
        res=start();
        if(res){
            log.info("start Serial success")
        }else{
            log.info("start Serial fail")
            return false;
        }
        return true;
    },
    getPortList,
    send,
    close,
    receive
}





electron调用vue页面

win.webContents.send('usbDataUpdate', global.json_data);
  mounted() {
     ipcRenderer.on('usbDataUpdate', (event, arg) => {
         this.usbDataUpdate(arg);
     });
  },
  beforeDestroy() {
    ipcRenderer.removeAllListeners('usbDataUpdate');
  },
  methods: {
    usbDataUpdate: function(json_data) {
      this.usbData=json_data;
      // console.log("更新USB数据"+typeof(json_data));
      // console.log("更新USB数据"+JSON.stringify(json_data));
    },
  }

你可能感兴趣的:(Web前端,Windows,vue.js,electron,前端)