背景
接了一个项目需要开发一个功能简单的桌面端应用,主要包含的功能有 内置数据,本地化操作数据,对数据进行CRUD操作。
效果展示如下:
技术选型:
-
开发桌面端有如下几种技术方案:**
Electron:使用HTML、CSS和JS构建跨平台的桌面应用程序,基于Chromium和Node.js。 NW.js:(也称node-webkit)类似于Electron。 React Native:使用React和JS。 Flutter:使用Dart编程语言,可构建高度定制的桌面应用程序。 本文这里选择Electron。
-
“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 应用程序中对数据进行本地持久化存储,你可以使用以下方法:
-
使用 Electron 的内置 API: Electron 提供了一些内置的 API,可以让你轻松地进行本地数据持久化存储,最常见的是使用
localStorage
和sessionStorage
,类似于在浏览器中的用法。这种方法适合于存储少量数据,例如配置信息或用户首选项。示例:
// 本地存储数据 localStorage.setItem('key', 'value'); // 从本地获取数据 const data = localStorage.getItem('key'); // 删除数据 localStorage.removeItem('key');
-
使用 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); });
-
使用数据库: 如果你需要存储大量结构化数据,你可以考虑使用一种数据库系统,如 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 应用程序中进行本地持久化存储的一些常见方法。
本项目使用
lowdb
,lowdb
是一个轻量级的本地JSON数据库,适合小型项目和简单数据持久化需求。
二.lowdb
的基本使用规则:
-
安装和导入:
在您的Electron项目中,首先确保安装了
lowdb
和lodash
依赖:npm install lowdb lodash
然后在您的JavaScript文件中导入
lowdb
和lodash
:const low = require('lowdb'); const FileSync = require('lowdb/adapters/FileSync'); const adapter = new FileSync('db.json'); // 指定数据文件 const db = low(adapter);
-
初始化数据库:
在初始化
lowdb
时,您可以使用defaults
方法来指定初始数据和默认值:db.defaults({ users: [] }).write();
这将创建名为
users
的初始数据对象,如果数据文件中不存在该对象,将使用默认值。 -
插入数据:
使用
push
方法将数据插入到数据库中:db.get('users') .push({ id: 1, name: 'Alice' }) .write();
-
查询数据:
使用
get
方法和value
方法查询数据:const users = db.get('users').value();
-
更新数据:
使用
find
方法和assign
方法来更新数据:db.get('users') .find({ id: 1 }) .assign({ name: 'Alicia' }) .write();
-
删除数据:
使用
remove
方法来删除数据:db.get('users') .remove({ id: 1 }) .write();
-
链式操作:
lowdb
支持链式操作,您可以在一个语句中执行多个操作,例如插入、查询和更新数据:db.get('users') .push({ id: 2, name: 'Bob' }) .find({ id: 2 }) .assign({ name: 'Bobby' }) .write();
-
使用过滤器:
您可以使用
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的官方文档!!!