0%

electron实战之批量注册工具

批量注册工具 based on Node.js & Electron

“披着app外衣的网页”

调试时,在项目根路径npm run start即可

使用时,由于打包后已包含相关node和chomium,用户无需安装其他软件,可直接打开使用

package.json

注意:

  1. main指定main process对应的js代码
  2. start指定启动方式
  3. package用于打包成exe可执行文件,配置可执行文件的存放路径、图标
  4. devDependencies是开发时所需的node module,不会一起打包
  5. dependencies是用户使用时所需的node module,会一起打包
    • 可以手动修改配置,规定依赖名和版本号,再在命令行npm install
    • 规定global才会全局安装npm module,否则只是本项目内
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
{
"name": "prefix-reg",
"version": "1.0.0",
"description": "A minimal Electron application",
"main": "index.js",
"scripts": {
"start": "nodemon --watch main.js --exec electron .",
"package": "electron-packager . prefix-reg --win --out ../prefix-reg --arch=x64 --app-version=1.0.0 --icon=./icon.ico"
},
"repository": "https://github.com/electron/electron-quick-start",
"keywords": [
"Electron",
"quick",
"start",
"tutorial",
"demo"
],
"author": "iic",
"license": "CC0-1.0",
"devDependencies": {
"electron": "^19.0.3",
"electron-packager": "^15.5.1",
"nodemon": "^2.0.16"
},
"dependencies": {
"axios": "^0.27.2",
"node-xlsx": "^0.21.0"
}
}

index.js

应用的主进程

不实现具体功能,只是把舞台搭起来

注意:

  1. 主进程和渲染进程间通过ipcMainipcRenderer通信
  2. 指定preload.js,供渲染进程调用api
  3. 指定main.html,窗体展示
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Modules to control application life and create native browser window
/* const { app, BrowserWindow } = require('electron')
const path = require('path') */

const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')

//拷贝electron官网的例子,用于选取文件
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (canceled) {
return
} else {
return filePaths[0]
}

}

//参考electron官网的例子,用于选取文件夹
async function handlePathOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] })
if (canceled) {
return
} else {
return filePaths[0]
}

}




function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

// and load the index.html of the app.
mainWindow.loadFile('./main.html')

// Open the DevTools.
// mainWindow.webContents.openDevTools()
}

// 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.whenReady().then(() => {
ipcMain.handle('dialog:openFile', handleFileOpen)
ipcMain.handle('dialog:openDirectory', handlePathOpen)
createWindow()
app.on('activate', function () {
// 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()
})
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

main.html

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
42
43
44
45
46
47
48
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src http://* ;script-src 'self'; style-src 'self' 'unsafe-inline'">
<link href="./styles.css" rel="stylesheet">
<title>前缀批量注册工具</title>
</head>

<body>
<h1>前缀批量注册</h1>

<table>
<tr>
<td><button type="button" id="images">选择图片文件夹</button></td>
<td>
<span>Images Directory path:</span>
</td>
<td>
<strong id="imagesPath"></strong>
</td>
</tr>

<tr>
<td><button type="button" id="sheet">选择xlsx文件</button></td>
<td>
<span>Excel File path:</span>
</td>
<td>
<strong id="sheetPath"></strong>
</td>
</tr>

<tr>
<td><button type='button' id='upload'>批量注册</button></td>
</tr>
</table>

<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>


</body>

</html>

styles.css

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
/* styles.css */

/* Add styles here to customize the appearance of your app */
body {
background-color: #ecb0b0;
}

h1 {
text-align: center;
color: #80624e;
}

table {
/* table-layout: fixed; */
width: 100%;
}

td {
height: 50px
}

button {
background-color: #ffebeb;
width: 200px;
border: none;
border-radius: 12px;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}


button:hover {
background-color: #bf9c94;
}

preload.js

出于安全考虑,渲染进程必须借助preload.js来调node api

借助axios来ajax上传数据

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
42
43
44
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.

window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}

for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})

const { contextBridge, ipcRenderer, remote, shell } = require('electron')
// 引入 node-xlsx 模块
const xlsx = require('node-xlsx')
const fs = require('fs')
const path = require('path')

const axios = require('axios')

contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
openDirectory: () => ipcRenderer.invoke('dialog:openDirectory'),
parseSheet: (excelFilePath) => xlsx.parse(excelFilePath),
readDir: (directory) => fs.readdirSync(directory),
joinPath: (a, b) => path.join(a, b),
parseImage: (imageFilePath) => fs.readFileSync(imageFilePath),
toBaseString: (image) => Buffer.from(image, 'binary').toString('base64'), // base64编码

requestHelper: (data) => axios.post('https://test-api.gdsinsing.com/safety/prefix/apply', data, {
headers: {
"Content-Type": "application/json"
}
})
.then((res) => {
console.log(`Status: ${res.status}`);
console.log('Body: ', res.data);
}).catch((err) => {
console.error(err);
})

})

renderer.js

在渲染进程中实现具体功能

只能通过调用预先在preload.js中写好的函数,来调用api

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// This file is required by the main.html file and will
// be executed in the renderer process for that window.
// No Node.js APIs are available in this process because
// `nodeIntegration` is turned off. Use `preload.js` to
// selectively enable features needed in the rendering
// process.



const btn1 = document.getElementById('images');
const btn2 = document.getElementById('sheet')
const btn3 = document.getElementById('upload');

const filePathElement1 = document.getElementById('sheetPath')
const filePathElement2 = document.getElementById('imagesPath')

var images = [] //存放图片
var applyDatas = [] //存放要注册的前缀信息

btn1.addEventListener('click', async () => {
const directoryPath = await window.electronAPI.openDirectory()
filePathElement2.innerText = directoryPath

const files = window.electronAPI.readDir(directoryPath);

var len = files.length;
for (let i = 0; i < len; i++) {
var a = Math.floor(i / 3) + 1;
var b = i % 3 + 1;
let imagePath = window.electronAPI.joinPath(directoryPath, a + '_' + b + '.png');
let image = window.electronAPI.parseImage(imagePath);
/* console.log(image);
console.log("============"); */
let string = window.electronAPI.toBaseString(image);
images.push(string);
}

})

btn2.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement1.innerText = filePath

//解析excel, 获取到所有sheets
const sheets = window.electronAPI.parseSheet(filePath);

/* // 查看页面数
console.log(sheets.length);
console.log('==================='); */
// 打印页面信息..
const sheet = sheets[0];
/* console.log(sheet); //{name: 'Sheet1', data: Array(15)}
console.log('===================');

// 打印页面数据
console.log(sheet.data); //an Array of Array
console.log('==================='); */

// 输出每行内容
var count = 0;
var len = sheet.data.length;
var rowLen = sheet.data[0].length;
for (let i = 1; i < len; i++) {
var row = sheet.data[i];
if (row.length != 0) {
var applyData = {};
applyData.companyName = row[0];
applyData.companyType = row[1];
applyData.companyCardNo = row[2];
applyData.agentName = row[3];
applyData.agentCardNo = row[4];
applyData.agentMobile = row[5];
applyData.agentEmail = row[6];
applyData.agentPost = row[7];
applyData.companyIndustry = row[8];
applyData.companySmallIndustry = row[9];
applyData.companyArea = row[10];
applyData.organContactAddress = row[11];
applyData.handle = row[12];

//图片需另外导入
applyData.ebl_img = images[count++];
applyData.agent_idcard_front = images[count++];
applyData.agent_idcard_reverse = images[count++];

applyDatas.push(applyData);
}
}

console.log(applyDatas);
})




btn3.addEventListener('click', async () => {

var len = applyDatas.length;
for (let i = 0; i < len; i++) {
var _data = {};
_data.applyData = applyDatas[i];
_data.sign = { "username": "test2", "pwd": "123456" };
const data = JSON.stringify(_data);

window.electronAPI.requestHelper(data);

}



/* var settings = {
"url": "https://test-api.gdsinsing.com/safety/prefix/apply",
"method": "POST",
"timeout": 0,
"headers": {
"Content-Type": "application/json"
},
"data": JSON.stringify(data)
}; */






/* axios.post('https://test-ssp.gdsinsing.com/safety/prefix/apply', data)
.then((res) => {
console.log(`Status: ${res.status}`);
console.log('Body: ', res.data);
}).catch((err) => {
console.error(err);
}); */




})