最近工作需要写一个数据录入的小工具,利用vue2+electron+sqlite的方式来实现。因为浏览器有安全权限问题是不能直接读取sqlite的,所以我们借助electron中 node的能力。他具有操作系统相关权限的接口。so…..

vue项目创建就忽略了。。。

环境:


node v18.20.2
npm 10.5.0
yarn  1.22.22
vue @vue/cli 5.0.8
如果node环境问题可以使用
nvm来进行node的管理,非常方便
nvm install 16(node版本)
nvm use  16 (切换到16这个版本)
1、按sqlite

npm i -S  sqlite3
2、安装electron

npm i -D electron@31.0.2   //-D 表示开发依赖  生产不依赖
3、安装electron forge 打包工具,也可用其他方式(electron-builder) 我这里使用这个。

npm install --save-dev @electron-forge/cli
npx electron-forge import   //npx 是执行包  npm是安装包管理


上面依赖安装完成后,根目录会出现forget.config.js 文件、main.js、以及package.json文件也会有相应的改动。

forget.config.js配置细则:

const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
const platform = process.platform;
module.exports = {
  packagerConfig: {
    ///额外资源目录
    extraResource: [
      './src/assets',
      './src/components',
    ],
    // 是否覆盖已经生成的文件
    overwrite: true,
     // 应用名称
    name: "一体机字段处理工具app",
    executableName: "all-in-one",
    // 打包平台,macOS 用 'darwin'
    platform: platform,//"darwin", //默认不设置是系统环境
    // 架构,对于大多数 macOS 应用来说是 'x64'  arch 
    //arch: "x64",  //默认不设置是系统环境
    // 应用程序的图标
    icon: "app",//platform == 'darwin' ? "app.icns" : "app.ico",自动寻找
    // 应用的版权说明
    appCopyright: 'Copyright © 2024 lion.com版权所有',
    // 应用程序的版本
    appVersion: "1.0.0",
    // macOS 的 bundle ID
    appBundleId: "com.electron.www.test",
    // macOS 的帮助文件 bundle ID
    helperBundleId: "com.electron.www.test.helper",
    // 打包输出目录
    out: "./out",
    // 是否打包为 ASAR 归档文件
    asar: true,
    win32metadata: {
      "ProductName": "五笔助手 Windows",
      "CompanyName": "kylebing.cn",
      "FileDescription": "五笔助手 for 小狼毫"
    }
  },
  rebuildConfig: {},
  makers: [
    {
      name: '@electron-forge/maker-squirrel',
      config: {
        "name": "一体机字段处理工具win",
      },
    },
    {
      name: '@electron-forge/maker-zip',
      platforms: ['darwin'],
    },
    {
      name: '@electron-forge/maker-dmg',
      config: {
        format: 'ULFO'
      }
    },
    // {
    //   name: '@electron-forge/maker-pkg',
    //   config: {
    //     // PKG 特定配置 Developer ID Installer: Your Name (Team ID) 打包指令:npx electron-forge make --target pkg
    //     identity: 'com.electron.www.test',
    //     format: 'ULFO'
    //   }
    // },
    {
      name: '@electron-forge/maker-deb',
      config: {},
    },
    {
      name: '@electron-forge/maker-rpm',
      config: {},
    },
  ],
  plugins: [
    {
      name: '@electron-forge/plugin-auto-unpack-natives',
      config: {},
    },
    // Fuses are used to enable/disable various Electron functionality
    // at package time, before code signing the application
    new FusesPlugin({
      version: FuseVersion.V1,
      [FuseV1Options.RunAsNode]: false,
      [FuseV1Options.EnableCookieEncryption]: true,
      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
      [FuseV1Options.EnableNodeCliInspectArguments]: false,
      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
 
};


package.json文件细则

{
  "name": "all-in-one",
  "description": "An electron-vue project",
  "productName": "一体机字段处理工具",
  "version": "0.1.0",
  "main": "main.js",
  "author": "Lion",
  "license": "MIT",
  "private": true,
  "scripts": {
    "serve": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
    "build": "vue-cli-service build",
    "electron": "export NODE_OPTIONS=--openssl-legacy-provider &&  electron .",
    "start": "electron-forge start",
    "package": "electron-forge package",
    "make": "electron-forge make",
    "make2": "electron-forge make --arch x64 --platform win32"
  },
  "dependencies": {
    "axios": "^1.7.2",
    "core-js": "^2.6.5",
    "electron-icns": "^3.6.3",
    "electron-squirrel-startup": "^1.0.1",
    "element-ui": "^2.15.14",
    "less": "^4.2.0",
    "less-loader": "^7.3.0",
    "locate-path": "^7.2.0",
    "path-exists": "^5.0.0",
    "sqlite3": "^5.1.7",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@electron-forge/cli": "^7.4.0",
    "@electron-forge/maker-deb": "^7.4.0",
    "@electron-forge/maker-dmg": "^7.4.0",
    "@electron-forge/maker-pkg": "^7.4.0",
    "@electron-forge/maker-rpm": "^7.4.0",
    "@electron-forge/maker-squirrel": "^7.4.0",
    "@electron-forge/maker-zip": "^7.4.0",
    "@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
    "@electron-forge/plugin-fuses": "^7.4.0",
    "@electron/fuses": "^1.8.0",
    "@vue/cli-plugin-babel": "^3.8.0",
    "@vue/cli-service": "^3.8.0",
    "electron": "^31.0.2",
    "vue-template-compiler": "^2.6.10"
  }
}

main.js 文件

const { app, BrowserWindow,ipcMain} = require('electron')
const { db ,dbClose } = require('./db.js');
const { ipcMainBind } = require('./ipcMainBind.js');
//import('./test.js');
const path = require('path');
const fs = require('fs');

// 获取 userData 目录的路径
const userDataPath = app.getPath('userData');
// 定义要保存的数据文件的名称
const dataFileName = 'userPreferences.json';
// 构建完整的文件路径
const dataFilePath = path.join(userDataPath, dataFileName);

const createWindow = () => {
    const win = new BrowserWindow({
      width: 1200,
      height: 800,
      //autoHideMenuBar: true, 
      webPreferences: {
        preload: path.join(__dirname, './preload.js'), // 指定预加载脚本路径
        nodeIntegration: false, // 禁用 Node.js 和预加载脚本互斥
        contextIsolation: true,//启用上下文隔离
      },
      //icon: path.join(__dirname, './app.ico'), // 图片
      icon: 'app.png', // 指定窗口图片
    })
    // 全屏
    //win.maximize()
    win.setMenu(null);
    //win.loadFile('index.html') 这里最后 vue 打包后 改为本地文件这样更流畅 这里我就用了localhost连接地址
    win.loadURL('http://localhost:8081') 
  }
  app.whenReady().then(() => {
    createWindow()
    ipcMainBind();
    app.on('activate', () => {
      if (BrowserWindow.getAllWindows().length === 0){
        createWindow()
      }
    })
    //生成文件  打印日志
    //log();
  })
  app.on('window-all-closed', () => {
    //dbClose();
    if (process.platform !== 'darwin') app.quit()
  })

  ///写入日志
  function log(){
     //写入日志示例数据
     const userData = {
      preferences: {
        theme: 'dark',
        language: 'zh-CN'
      }
    };

    //判断文件是否存在
    userDataPath && fs.existsSync(userDataPath) || fs.mkdirSync(userDataPath);

    // 将数据保存到文件
    fs.writeFile(dataFilePath, JSON.stringify(userData), (err) => {
      if (err) throw err;
      console.log('用户数据已保存到', dataFilePath);
    });
  }
完成后。查看package.json中,先启动 vue网站

npm run serve
再运行electron 程序可以本地打开一个桌面的调试程序

npm run start
//或者 npx 包执行命令来执行也可行
npx electron-forge start
ok这时候不出意外哈喽我的 应该出现了。。。

再来看看sqlite的代码部分。首先vue中是不能操作sqlite的。。所以我们用到预加载的功能,使用进程通信才能实现。因为我们vue 是渲染进程,electron中sqlite是在主进程中。。

首先新建一个 preload.js,代码有注释 不描述了。。

// preload.js
const { contextBridge, ipcRenderer } = require('electron');
//暴漏了一个electronAPI 给渲染进程调用
contextBridge.exposeInMainWorld('electronAPI', {
  ///异步返回 在vue中使用的代码
  //  console.log('window.electron='+window.electronAPI);
  //  window.electronAPI.send("insert","wo lai le");
  //  window.electronAPI.receive("insert", (msg) => {
  //    console.log(`Received ${msg} from main process`);
  //  });
  send:  (channel, data) => {
    console.log('preload.js send:'+channel+"=="+data);
    ipcRenderer.send(channel, data);
  },
  //返回结果
  receive: (channel, func) => {
    //渲染进程
    console.log('preload.js receive:'+channel+"=="+func);
    //判断是否已经监听
    if (ipcRenderer.listenerCount(channel) > 0) {
      return;
    }
    ipcRenderer.on(channel, (event, ...args) => func(...args));
    // ipcRenderer.on(channel,function(event,...args){
    //   console.log('preload.js receive:'+channel+"=="+a);
    // });
  },
  //移除监听
  removeListener: (channel) => {
    ipcRenderer.removeListener(channel);
  },
  //移除全部监听
  removeAllListeners: () => {
    ipcRenderer.removeAllListeners();
  },
  //异步返回结果 和上面相反
   //异步返回 在vue中使用的代码
  //  query2(){
  //   window.electronAPI.sendSynchronization('query2', 'bai').then((data) => {
  //     console.log('data='+data);
  //     console.log('data='+JSON.stringify(data));
  //   });
  // }
  sendSynchronization : async (channel,key) =>{
    try {
      var result = await ipcRenderer.invoke(channel, key);
      console.log('Received result:', result);
    } catch (error) {
      console.error('Error requesting data from main process:', error);
    }
    return result;
  }
});





还要注册一个频道监听icpmain.js

const {ipcMain} = require('electron')
const { db,query,insertAsync,update } = require('./db.js');
exports.ipcMainBind = function(){

    ipcMain.on('insert', async (event, arg) => {
        console.log('insert='+arg);
        //insert( arg );
        let res = await insertAsync( arg );
        //event.returnValue = insert(arg);
        //event.reply('insert', 'insert success');
        event.sender.send('insert', res);
    });

    ipcMain.on('query', async (event, arg) => {
        console.log('query='+arg);
        let res = await query( arg );
        //event.reply('query', 'query success');
        event.sender.send('query', res );
       // event.returnValue = 'query success';
    });
    ///要在ready 之后 才能使用  
    //同步等待
    ipcMain.handle('query2', async (event, ...args) => {
        let res = await query( args[0] );
        return res; // 将结果返回给渲染进程
    });
    ipcMain.on('update', async (event, arg) => {
        console.log('update='+arg);
        let res = await update( arg );
        //event.reply('update', 'update success');
        event.sender.send('update', res );
       // event.returnValue = 'update success';
    });

}
里面使用db的调用函数,db是我自定义的数据库操作函数。。db.js如下

///数据库初始化
let DatabaseInstance = (function() {
    let instance;
    function createInstance() {
        //// 引入 sqlite3 并设置为 verbose 模式,以便能够看到更详细的日志
        const sqlite3 = require('sqlite3').verbose();
        let db = new sqlite3.Database('/Users/baijinhao/www/testvUE/DD/src/assets/yitiji.db', (err) => {
            if (err) {
                console.error('Error opening database', err);
            } else {
                console.log('Database opened successfully');
                // 在这里,您可以执行数据库初始化操作,例如创建表
                db.run("CREATE TABLE IF NOT EXISTS contacts (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, email TEXT)");
    
            }
        });
        return db;
    }
    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

exports.db = DatabaseInstance.getInstance();

exports.insertAsync = async function (arg) {
    await exports.db.run('INSERT INTO data (value,key) VALUES (?,?)',arg.value,arg.key);
   // console.log('insert success');
    return 'insert success';
}

exports.query = async function (arg){
    return new Promise((resolve, reject) => {
        exports.db.all("SELECT * FROM data where key='"+arg+"'", [], (err, rows) => {
            if (err) {
                console.error('Error running query', err);
                reject(err);
            } else {
                //console.log('query success');
                //console.log('query success'+JSON.stringify(rows));
                resolve(rows);
            }
        });
    });
}  

exports.update = async function (arg){
    return new Promise((resolve, reject) => {
        exports.db.all("update data set value = ? where key = ? ", [arg.value,arg.key], (err, rows) => {
            if (err) {
               // console.error('Error running update', err);
                reject(err);
            } else {
              //  console.log('update success');
                resolve("update success");
            }
        });
    });
}  


exports.dbClose = function (){
    exports.db.close((err) => {
        if (err) {
            console.error('Error closing database', err);
        } else {
            console.log('Database closed successfully');
        }
    });
}




vue 界面的调用

 created() {
 //这是监听后的结果
    window.electronAPI.receive("update", (msg) => {
      this.$message({
        message: '更新信息成功',
        type: 'success'
      });
    });
    window.electronAPI.receive("insert", (msg) => {
      this.$message({
        message: '插入信息成功',
        type: 'success'
      });
    });
    window.electronAPI.receive("query", (data) => {
      console.log(JSON.stringify(data));
      console.log(data.length)
      if (data && data.length > 0) {
        this.data = JSON.parse(data[0]['value']);
      } else {
        this.insert();
      }
    });
    this.query();
  },
  },
  methods:{
     async insert() {
     //调用主进程的暴漏的函数
      window.electronAPI.send('insert', { value: JSON.stringify(this.data), key: this.table });
    },
    async query() {
         //调用主进程的暴漏的函数
      window.electronAPI.send('query', this.table);
    },
    async update() {
         //调用主进程的暴漏的函数
      console.log(this.data);
      window.electronAPI.send('update', { value: JSON.stringify(this.data), key: this.table });
    },
    query2(){
    //这是另外的方式  不需要注册receive函数同步等待
     window.electronAPI.sendSynchronization('query2', 'bai').then((data) => {
       console.log('data='+data);
       console.log('data='+JSON.stringify(data));
     });
   }
  }
OK这里sqlite的操作结束完毕。

最后就是打包生成,dmg,app,exe 操作。在上面forge.config.js中已经配置完成。所以我们最后需要执行 

npm run make //就可以之心打包操作
我配置的打包地址是根目录下的out文件,所以打包完成后文件会保存在这里。

最后看看我的效果吧。

本源码已经放置地址:https://gitee.com/codeceo_net/vue2-electron-sqlite.git(release分支)

安装依赖:npm install 

有兴趣可以下载来玩玩。。。。