背景

接了一个项目需要开发一个功能简单的桌面端应用,主要包含的功能有 内置数据,本地化操作数据,对数据进行CRUD操作。
效果展示如下:
在这里插入图片描述
在这里插入图片描述

技术选型:

  1. 开发桌面端有如下几种技术方案:**

    Electron:使用HTML、CSS和JS构建跨平台的桌面应用程序,基于Chromium和Node.js。
    NW.js:(也称node-webkit)类似于Electron。
    React Native:使用React和JS。
    Flutter:使用Dart编程语言,可构建高度定制的桌面应用程序。
    
    本文这里选择Electron。 
    
  2. “vue”: “^3.2.47”,
    “vue-router”: “^4.1.6”,
    “ant-design-vue”: “^4.0.0”,
    “lowdb”: “^1.0.0”, //本地化存储数据插件

项目搭建

本项目使用Vue Cli 创建Vue项目,命令如下:

npm i @vue/cli -g
vue create desk
cd desk
vue add electron-builder
npm run electron:serve

运行之后效果:
在这里插入图片描述
项目目录如下:
和vue项目目录差不多,差异的是electron主进程的代码是放在background.js中。
在这里插入图片描述

项目难点:

一.Electron 实现对数据进行本地持久化存储方案选择——lowdb

Electron 是一个用于构建跨平台桌面应用程序的开源框架,它允许你使用前端技术(HTML、CSS 和 JavaScript)创建桌面应用程序。如果你想在 Electron 应用程序中对数据进行本地持久化存储,你可以使用以下方法:

  1. 使用 Electron 的内置 API: Electron 提供了一些内置的 API,可以让你轻松地进行本地数据持久化存储,最常见的是使用 localStoragesessionStorage,类似于在浏览器中的用法。这种方法适合于存储少量数据,例如配置信息或用户首选项。

    示例:

    // 本地存储数据
    localStorage.setItem('key', 'value');
    
    // 从本地获取数据
    const data = localStorage.getItem('key');
    
    // 删除数据
    localStorage.removeItem('key');
    
  2. 使用 Node.js 模块: Electron 可以使用 Node.js 模块,这意味着你可以使用 Node.js 提供的文件系统和其他模块来进行更高级的本地数据持久化存储。最常见的方式是使用 fs 模块来读写文件。

    示例:

    const fs = require('fs');
    
    // 写入数据到本地文件
    fs.writeFile('data.txt', 'Hello, Electron!', (err) => {
      if (err) throw err;
      console.log('数据已写入文件');
    });
    
    // 从本地文件读取数据
    fs.readFile('data.txt', 'utf8', (err, data) => {
      if (err) throw err;
      console.log('从文件中读取的数据:', data);
    });
    
  3. 使用数据库: 如果你需要存储大量结构化数据,你可以考虑使用一种数据库系统,如 SQLite、IndexedDB 或 LevelDB。SQLite 特别适合桌面应用程序,因为它是一个嵌入式数据库引擎,可以轻松地集成到 Electron 应用中。

    示例(使用 SQLite):

    const sqlite3 = require('sqlite3').verbose();
    const db = new sqlite3.Database('mydatabase.db');
    
    // 创建表并插入数据
    db.serialize(() => {
      db.run('CREATE TABLE IF NOT EXISTS users (id INT, name TEXT)');
      db.run('INSERT INTO users VALUES (1, "John")');
    });
    
    // 查询数据
    db.all('SELECT * FROM users', (err, rows) => {
      if (err) throw err;
      console.log('查询结果:', rows);
    });
    
    // 关闭数据库连接
    db.close();
    

以上是在 Electron 应用程序中进行本地持久化存储的一些常见方法。

本项目使用lowdblowdb是一个轻量级的本地JSON数据库,适合小型项目和简单数据持久化需求。

二.lowdb的基本使用规则:

  1. 安装和导入:

    在您的Electron项目中,首先确保安装了lowdblodash依赖:

    npm install lowdb lodash
    

    然后在您的JavaScript文件中导入lowdblodash

    const low = require('lowdb');
    const FileSync = require('lowdb/adapters/FileSync');
    const adapter = new FileSync('db.json'); // 指定数据文件
    const db = low(adapter);
    
  2. 初始化数据库:

    在初始化lowdb时,您可以使用defaults方法来指定初始数据和默认值:

    db.defaults({ users: [] }).write();
    

    这将创建名为users的初始数据对象,如果数据文件中不存在该对象,将使用默认值。

  3. 插入数据:

    使用push方法将数据插入到数据库中:

    db.get('users')
      .push({ id: 1, name: 'Alice' })
      .write();
    
  4. 查询数据:

    使用get方法和value方法查询数据:

    const users = db.get('users').value();
    
  5. 更新数据:

    使用find方法和assign方法来更新数据:

    db.get('users')
      .find({ id: 1 })
      .assign({ name: 'Alicia' })
      .write();
    
  6. 删除数据:

    使用remove方法来删除数据:

    db.get('users')
      .remove({ id: 1 })
      .write();
    
  7. 链式操作:

    lowdb支持链式操作,您可以在一个语句中执行多个操作,例如插入、查询和更新数据:

    db.get('users')
      .push({ id: 2, name: 'Bob' })
      .find({ id: 2 })
      .assign({ name: 'Bobby' })
      .write();
    
  8. 使用过滤器:

    您可以使用filter方法来过滤数据:

    const filteredUsers = db.get('users')
      .filter(user => user.name.includes('B'))
      .value();
    

了解更多详细信息可看lowdb的文档。

三. 读取本地数据

  • background.js文件中实现本地数据读取代码如下:
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import fs from 'fs-extra'
import localDb from './local-db.json'

const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')  //获取electron应用的用户目录
// 判断路径是否存在,若不存在,就创建一个新的
if (process.type !== 'renderer') {
  if (!fs.pathExistsSync(STORE_PATH)) {
    fs.mkdirpSync(STORE_PATH)
  }
}
const adapter = new FileSync(path.join(STORE_PATH, 'localData.json'))
let db = Datastore(adapter) // lowdb接管该文件数据
db._.mixin(LodashId)  //LodashId自动生成id
db.defaults(localDb).write() // 一定要显式调用write方法将数据存入JSON

四. 对数据进行操作,就涉及到主进程与渲染进程之间进行通信

  • background.js文件中监听渲染进程的请求代码如下:
// 监听渲染进程的请求
ipcMain.on('ipc-getSurfaceData', (event) => {
  // 执行数据库写入操作
  try {
    const data = db.get('SurfaceData').value()
    // 读取成功的响应给渲染进程
    event.sender.send('getSurface', { success: true, res: data });
  } catch (error) {
    // 发送写入失败的响应给渲染进程
    return event.sender.send('getSurface', { success: false, error: error.message });
  }
});
  • 渲染进程 src/api/service.js 发送请求给主进程执行数据库写入操作如下:
    这里有点类似mock 模拟发送请求,不过这里是向主进程发送请求,因为只有主进程才有对本地数据进行增删改查的操作。
export function getSurfaceData() {
  // 发送请求给主进程执行数据库写入操作
  ipcRenderer.send('ipc-getSurfaceData');
  return new Promise((resolve, reject) => {
    // 监听来自主进程的响应
    ipcRenderer.on('getSurface', (event, res) => {
      if (res.success) {
        resolve({
          code: 200,
          data: res.res
        })
      } else {
        return reject({
          code: 400,
          message: res.error
        })
      }
    });
  })
}
  • vue文件下调用渲染进程的方法,代码如下:
const getSurface = async () => {
  const res = await getSurfaceData()
  if (res.code == 200) {
    SurfaceData.value = res.data
    innerSurfaceData.value = SurfaceData.value.filter(
      (item) => item.type == 'inner'
    )
    outSurfaceData.value = SurfaceData.value.filter((item) => item.type == 'out')
  } else {
    message.error(res.message)
  }
}

以上就是使用lowdb对本地数据进行操作的具体实现。

五. 打包相关配置

在什么系统下打包就会生成相应系统下的桌面端,也可以执行命令去打包相应的桌面端
page.json 添加如下打包命令:
“electron:build:win32”: “vue-cli-service electron:build --mode --win --ia32”,
“electron:build:win64”: “vue-cli-service electron:build --mode --win --x64”,
“electron:build:mac”: “vue-cli-service electron:build --mode --mac”,

  • vue.config.js 文件代码如下:
module.exports = {
  lintOnSave: true,
  productionSourceMap: false,
  pluginOptions: {
    electronBuilder: {
      nodeIntegration: true,
      chainWebpackMainProcess: (config) => {
        config.output.filename((file) => {
          if (file.chunk.name === 'index') {
            return 'background.js';
          } else {
            return '[name].js';
          }
        });
      },
      builderOptions: {
        appId: 'com.electron.htc',//包名
        buildVersion: '20230726',
        productName: 'xxx软件',//生成exe的名字
        copyright: 'Copyright © 2023- year',
        icon: 'static/favicon.ico',
        extraFiles: [
          {
            from: 'src/assets/favicon.ico',
            to: 'static/favicon.ico',
          },
        ],
        win: {
          icon: 'src/assets/favicon.ico',
          executableName: 'xxx软件',
          requestedExecutionLevel: 'requireAdministrator',
        },
        nsis: {
          oneClick: false,// 是否一键安装
          perMachine: true, //代表是否显示辅助安装程序的安装模式安装程序页面(选择按机器还是按用户)。true时代表始终按用户安装。
          artifactName:
            'xxx软件V${version}_Build${buildVersion}.${ext}',
          uninstallDisplayName: 'xxx软件 ${version}',
          allowToChangeInstallationDirectory: true, //是否允许修改安装目录
          createDesktopShortcut: true, // 是否创建桌面图标
          createStartMenuShortcut: true,// 是否创建开始菜单图标
          shortcutName: "xxx软件", // 快捷方式名称
          runAfterFinish: false,//是否安装完成后运行
          menuCategory: 'xxx有限公司',
        },
      },
    },
    // chainWebpackMainProcess: (config) => {
    //   config.output.filename('background.js');
    // }
  },
  css: {
    loaderOptions: {
      less: {
        lessOptions: {
          javascriptEnabled: true,
        },
      },
    },
  },
}

这就是我第一次开发桌面端的经历,搭建的项目复杂程度仅供小型桌面端使用。复杂一些的项目,就需要考虑主进程再去细分,一个background.js难以满足。详细的可以看electron的官方文档!!!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐