Electron-vue桌面端开发总结
# 1 网络文件下载至指定地点
# 1.1 使渲染进程使用NodeJS
- 在vue.config 里添加相关内容将
electronBuilder
作为一个插件进行参数配置
pluginOptions:{
electronBuilder:{
nodeIntegration:true
}
}
1
2
3
4
5
2
3
4
5
background.js
的createWindow
方法中的webPreferences
添加webSecurity:false
。将浏览器安全策略关闭,使electron
中的浏览器能够使用NodeJS
# 1.2 使用NodeJS的fs模块与axios请求网络文件并保存
- 组件引入fs
import fs from 'fs'
- 将
node_modules
里axios/lib
的http.js
内容覆盖xhr.js
,为了能够使用后端axios
流形式下载 axios
请求后fs下载到指定地址
axios({
method:'get',
url:this.res.data.data[0].middleURL,
responseType:'stream'
}).then((response)=>{
response.data.pipe(fs.createWriteStream('E:/code/dog.jpg'))
})
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2 渲染进程开启新窗口
# 2.1 配置新窗口路由
其中router必须是Hash形式,否则会报错
const router = createRouter({
//必须是hash形式
history: createWebHashHistory(),
routes,
});
1
2
3
4
5
2
3
4
5
# 2.2 渲染进程向主进程通信
不需要考虑vue组件的问题,子组件也可以直接与主进程通讯
渲染进程
import { ipcRenderer } from "electron";
const CreateWindow = function(){
ipcRenderer.send("sumbit", "子进程给主进程发送数据");
}
1
2
3
4
2
3
4
主进程
import { ipcMain } from 'electron'
ipcMain.on('sumbit',(event,arg)=>{
event.reply('reply',"主进程回复")
console.log(event)
console.log(arg)
})
1
2
3
4
5
6
2
3
4
5
6
# 2.3 主进程打开新窗口
需要根据构建和打包的工具进行调整,但原则就是开发的时候获取localhost:端口
,生产的时候获取Electron
的代理
ipcMain.on('sumbit',(event,arg)=>{
event.reply('reply',"主进程回复")
console.log(event)
console.log(arg)
let modal = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
webSecurity:false
}
})
//开发环境
if (process.env.WEBPACK_DEV_SERVER_URL) {
modal.loadURL(process.env.WEBPACK_DEV_SERVER_URL+ "login")
} else {
//生产环境
createProtocol('app')
// Load the index.html when not in development
modal.loadURL('app://./index.html/#/login')
}
modal.on("closed", () => {
modal = null;
});
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 3 文件监听
# 3.1 文件夹文件监听
- 增加、删除和修改文件名的事件为
rename
,文件修改的事件为change
- 其中~文件以及.文件需要判断处理,不是所有的都执行后续的文件信息获取函数
fs.watch(folderUrl.value, (eventType, filename) => {
changeEvent.value = eventType;
changeFile.value = filename;
console.log(eventType, filename);
// 执行文件状态读取函数
ReadLocal();
});
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3.2 文件夹文件信息获取
- 读取文件夹里的所有文件,遍历文件获取其文件信息
const ReadLocal = function () {
fs.readdir(folderUrl.value + "/", (err, files) => {
if (err) console.warn(err);
else {
FileData.value = [];
files.forEach((file) => {
fs.stat(folderUrl.value + "/" + file, (err, stats) => {
if (err) console.warn(err);
else {
const filedata = {
name: file,
url: folderUrl.value + file,
ctime: stats.ctime,
atime: stats.atime,
mtime: stats.mtime,
ctimeMs: stats.ctimeMs,
mtimeMs: stats.mtimeMs,
size: stats.size,
directory: stats.isDirectory(),
};
FileData.value.push(filedata);
}
});
});
}
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 3.3 文件夹叠套获取文件信息
通过递归的方法从文件夹层层向下
ReadLocal(FileData, folderUrl.value);
// 读取本地文件状态
const ReadLocal = function (object, folder) {
fs.readdir(folder + "\\", (err, files) => {
if (err) console.warn(err);
else {
files.forEach((file) => {
if (file[0] != "." && file[0] != "~") {
FileStatus(object, folder, file);
}
});
}
});
};
// 文件状态扫描
const FileStatus = function (object, folder, file) {
fs.stat(folder + "\\" + file, (err, stats) => {
if (err) console.warn(err);
if (stats.isDirectory()) {
object[file] = {};
ReadLocal(object[file], folder + "\\" + file);
} else {
const filedata = {
name: file,
url: folder + "\\" + file,
ctime: stats.ctime,
atime: stats.atime,
mtime: stats.mtime,
ctimeMs: stats.ctimeMs,
mtimeMs: stats.mtimeMs,
size: stats.size,
directory: stats.isDirectory(),
};
object[file] = filedata;
FileDataList.value.push(filedata);
}
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 3.4 文件夹文件更新状态判断
# 3.4.1 筛选文件变更事件
因为fs.watch()
在新建一个文档时会被多次调用,且其实根据地址下的文件名来更新的,存在.
开头的不需要同步的文件夹、文件,以及.tmp
、.TMP
结尾的过程文件更改也会被调用,所以需要在文件监听这里添加过滤机制
pointIndex - slashIndex > 1
各级文件夹下以.
开头的文件忽略filename[slashIndex + 1] != "~"
各级文件夹下以~
开头的文件忽略!filename.endsWith(".tmp") && !filename.endsWith(".TMP")
以.tmp
、.TMP
结尾的过程文件忽略now - time > interval || changeFile.value != filename
间隔时间过短且变更的文件名与上一次一致的忽略
// 文件更新频率过高忽略
const interval = 100;
let time = new Date().getTime();
// 文件监听
fs.watch(folderUrl.value, { recursive: true }, (eventType, filename) => {
// console.log(FileDataList.value);
let now = new Date().getTime();
let slashIndex = filename.lastIndexOf("\\");
let pointIndex = filename.lastIndexOf(".");
if (
pointIndex - slashIndex > 1 &&
filename[slashIndex + 1] != "~" &&
!filename.endsWith(".tmp") &&
!filename.endsWith(".TMP")
) {
if (now - time > interval || changeFile.value != filename) {
time = now;
changeEvent.value = eventType;
changeFile.value = filename;
// console.log(eventType);
// console.log(filename);
StatusUpdate(filename);
}
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 3.4.2 文件变更分类且更新文件状态
- 如果找不到该文件证明被删除;将文件信息从
FileDataList
中删除 - 找得到文件且该文件路径已经存储在
FileDataList
中,证明文件被修改;将文件信息更新至FileDataList
中 - 找得到文件但改文件路径未被存储在
FileDataList
中,证明文件新增;将文件信息添加到FileDataList
中
注意
由于目前的文件变更分类依靠文件地址,存在文件重命名无法分辨的问题。文件重命名会被辨识为原文件被删除,新增了该名后的文件
以后考虑要从后端获取文件的ID,与文件地址混合判断
const StatusUpdate = function (file) {
let url = folderUrl.value + "\\" + file;
fs.stat(url, (err, stats) => {
if (err) {
console.log("删除了" + file + "文件");
for (let index = 0; index < FileDataList.value.length; index++) {
if (FileDataList.value[index]["url"] == url) {
FileDataList.value.splice(index, 1);
console.log(FileDataList.value);
}
}
} else {
const filedata = {
name: file.split("\\").pop(),
url: url,
ctime: stats.ctime,
atime: stats.atime,
mtime: stats.mtime,
ctimeMs: stats.ctimeMs,
mtimeMs: stats.mtimeMs,
size: stats.size,
directory: stats.isDirectory(),
};
for (let index = 0; index < FileDataList.value.length; index++) {
if (FileDataList.value[index]["url"] == filedata["url"]) {
if (FileDataList.value[index]["mtimeMs"] != filedata["mtimeMs"]) {
console.log("修改了" + file + "文件");
FileDataList.value[index]["mtime"] = filedata["mtime"];
FileDataList.value[index]["mtimeMs"] = filedata["mtimeMs"];
FileDataList.value[index]["size"] = filedata["size"];
}
break;
}
if (!FileDataList.value[index + 1]) {
console.log("新增了" + file + "文件");
FileDataList.value.push(filedata);
}
}
}
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4 搜索注册表并且启动对应软件
# 4.1 搜索注册表并固化
# 4.1.1 搜索注册表并保存bat
- 从卸载目录里面寻找软件是否安装,以及安装的信息
%1
第一个参数是保存至本地的文件路径+文件名%2
第二个参数是对应软件的注册表key
@echo off
@REM 将之前的固化注册表信息清空
type nul > %1
@REM ·连接需要查找的注册表键
set str=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%2
@REM ·reg query %str% 用来获取注册表指定软件的安装信息
@REM ·for /f "tokens=1,2*" %%i in 按照行循环,并且按照空格将一行的内容分割成3分,忽略第二个空格后的所有空格
for /f "tokens=1,2*" %%i in ('reg query %str%') do (
@REM ·将3个部分用,隔开逐行写入固化注册表文件
echo %%i,%%j,%%k>>%1
)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 4.1.2 渲染进程执行bat文件
exec
执行bat时通过空格进行参数的分割exec
执行时需要bat的绝对路径而非相对路径- 这里使用
process.cwd()
能够通过开发环境,但不能通过生产环境
import { exec } from "child_process";
const GetRegistration = function (software) {
const url = folder.value + software["DisplayName"] + ".txt";
console.log("__dirname" + __dirname);
console.log("__filename" + __filename);
console.log("process.cwd()" + process.cwd());
exec(
process.cwd() + "/public/reg1.bat " + url + " " + software["ID"],
(error, stdout, stderr) => {
if (error) {
console.error("error: " + error);
return;
}
console.log("stdout: " + stdout);
console.log("stderr: " + typeof stderr);
ReadRegFile(url, software);
}
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.2 读取固化的注册表内容并记录在前端
# 4.2.1 前端用于维护的应用信息
const softwareStats = ref({
AECmate: {
DisplayName: "AECMate",
Name: "AnyBuidingCAD.exe",
ID: "{61CDF82A-16BB-4FD5-9B8A-5B12C8349D8E}",
Version: "",
InstallLocation: "",
Install: false,
},
Revit: {
DisplayName: "Revit",
Name: "Revit.exe",
ID: "{61CDF82A-16BB-4FD5-9B8A-5B12C8349D8E1}",
Version: "",
InstallLocation: "",
Install: false,
},
Wukong: {
DisplayName: "Wukong",
Name: "WuKong.exe",
ID: "{85B05B30-FA7A-4698-80F3-022F489ABAEE}",
Version: "",
InstallLocation: "",
Install: false,
},
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4.2.2 读取固化注册表并修改softwareStats
const ReadRegFile = function (url, software) {
fs.readFile(url, "utf-8", (err, result) => {
//通过判断文件是否存在确定应用是否安装
if (err) {
console.error("error: " + err);
software["Install"] = false;
} else {
software["Install"] = true;
//将信息按照行分割
let list = result.split("\r\n");
//行信息遍历获取需要的
list.forEach((item) => {
if (item.includes("InstallLocation")) {
software["InstallLocation"] = item.split(",")[2];
}
if (item.includes("Version")) {
software["Version"] = item.split(",")[2];
}
});
}
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4.3 启动软件
- 通过`"${}"`将字符串强制用
""
包裹,这样可以避免运行路径里面存在空格导致无法执行的问题
const StartSoftware = function (software) {
exec(
`"${software["InstallLocation"] + software["Name"]}"`,
(error, stdout, stderr) => {
if (error) {
console.error("error: " + error);
return;
}
console.log("stdout: " + stdout);
console.log("stderr: " + typeof stderr);
}
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
上次更新: 2023/07/31, 19:25:37