创建react项目
create-react-app calculator
引入electron
yarn add electron --dev
引入antd
yarn add antd
引入electron-is-dev,用来判断当前是开发环境还是生产环境
yarn add electron-is-dev
在public目录下加入electron.js, preload.js
electron.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, Menu } = require("electron");
const path = require("path");
const isDev = require("electron-is-dev");
let mainWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, "preload.js"),
},
});
// 清除顶部菜单
Menu.setApplicationMenu(null);
if (isDev) {
mainWindow.loadURL("http://localhost:3000");
mainWindow.openDevTools();
// mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile("./build/index.html");
}
mainWindow.on("closed", function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
// 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(() => {
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.
preload.js
// 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]);
}
});
修改package.json
{
...
"main": "public/electron.js",
"homepage": ".",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"electron": "electron ."
}
...
}
运行程序
# 运行react
yarn start
# 运行electron
yarn electron
每次都运行两个命令很麻烦,使用concurrently和wait-on,将两个命令放在一起运行
yarn add concurrently wait-on --dev
修改package.json
{
...
"scripts": {
"electron": "concurrently \"react-scripts start\" \"wait-on http://localhost:3000 && electron .\""
}
...
}
客户端渲染使用react
计算器一般分为两块,一块显示计算结果,一块为按钮操作区,新建两个组件input-button.js, input-text.js
// # input-text.js
import React, { Component } from "react";
import { Input } from "antd";
const { TextArea } = Input;
class inText extends Component {
render() {
return (
);
}
}
export default inText;
// # input-button.js
import React, { Component } from "react";
import { Button } from "antd";
import PropTypes from "prop-types";
class inButton extends Component {
static propTypes = {
className: PropTypes.string,
};
static defaultProps = {
className: "same_size",
};
render() {
return (
);
}
}
export default inButton;
App.js
import React, { Component } from "react";
import Button from "./input-button";
import Text from "./input-text";
class App extends Component {
constructor(props) {
super(props);
this.state = {
string: "",
};
this.handleButton = this.handleButton.bind(this);
}
handleButton(e) {
if (e.target.value !== undefined) {
let instring = e.target.value;
let prvcontent = this.state.string;
let content = "";
if (
instring === "+" ||
instring === "-" ||
instring === "*" ||
instring === "/"
) {
content = prvcontent + " " + instring + " ";
} else if (instring === "附加") {
content = "";
} else if (instring === "C") {
content = "";
} else if (instring === "Back") {
if (prvcontent) {
let newcontent = String(prvcontent);
if (
newcontent[newcontent.length - 1] === " " &&
newcontent[newcontent.length - 3] === " "
) {
prvcontent = newcontent.slice(0, newcontent.length - 3);
} else {
prvcontent = newcontent.slice(0, newcontent.length - 1);
}
}
content = prvcontent;
} else if (instring === "=") {
if (prvcontent) {
if (prvcontent.indexOf(" ") !== -1) {
let arr = prvcontent.split(" ");
let ans = [];
let i = 0;
while (i < arr.length) {
if (arr[i] === "") {
i++;
} else if (arr[i] === "+") {
ans.push(arr[i + 1]);
i += 2;
} else if (arr[i] === "-") {
ans.push(-arr[i + 1]);
i += 2;
} else if (arr[i] === "*") {
let a;
let b = ans.pop();
if (arr[i + 1] === "-") {
a = -arr[i + 2];
i += 3;
} else {
a = arr[i + 1];
i += 2;
}
ans.push(b * a);
} else if (arr[i] === "/") {
let a;
let b = ans.pop();
if (arr[i + 1] === "0") {
content = "ERROR!";
return;
} else if (arr[i + 1] === "-") {
a = -arr[i + 2];
i += 3;
} else {
a = arr[i + 1];
i += 2;
}
ans.push(b / a);
} else {
ans.push(arr[i]);
i++;
}
}
let fin_ans = parseFloat(ans[0]);
for (i = 1; i < ans.length; i++) {
fin_ans += parseFloat(ans[i]);
}
content = fin_ans;
} else {
content = prvcontent;
}
} else {
content = "";
}
} else {
if (prvcontent && parseInt(prvcontent) !== 0) {
content = prvcontent + instring;
} else {
content = instring;
}
}
this.setState({
string: content,
});
}
}
render() {
return (
);
}
}
export default App;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./utils/reportWebVitals";
ReactDOM.render( , document.getElementById("root"));
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
index.css
@import '~antd/dist/antd.css';
#main{
margin: 0 auto;
width: 100%;
height: 100vh;
}
#input_text{
padding: 3%;
height: 25%;
display: flex;
}
#input_button{
/*border: 1px solid black;*/
margin: 0 2%;
width: 96%;
height: 70%;
display: flex;
flex-wrap: wrap;
}
#input_text #content{
flex: auto;
resize: none;
margin: 0;
width: 100%;
padding: 10px;
font-size: 2vw;
border: 1px solid #d9d9d9;
}
#input_button .same_size{
flex: auto;
margin: 1%;
width: 18%;
height: 20%;
font-size: 2vw;
}
#input_button .equal_size{
flex: auto;
margin: 1%;
width: 38%;
height: 20%;
font-size: 1.5vw;
}
运行项目可以看到客户端
yarn electron
打包
引入electron-builder
yarn add electron-builder --dev
修改package.json打包配置
"build": {
"productName": "react electron antd",
"appId": "com.charming",
"asar": true,
"files": [
"build/**/*"
],
"dmg": {
"artifactName": "react_electron_antd.dmg",
"contents": [
{
"type": "link",
"path": "/Applications",
"x": 410,
"y": 150
},
{
"type": "file",
"x": 130,
"y": 150
}
]
},
"nsis": {
"oneClick": false, // 一键安装(安装在C盘)
"allowToChangeInstallationDirectory": true, // 允许自定义安装
"shortcutName": "react-electron-antd"
},
"mac": {
"target": "dmg",
"icon": "icon.ico"
},
"win": {
"target": "nsis",
"icon": "icon.ico"
},
"directories": {
"output": "dist/" // 打包输出路径
}
},
加入打包命令
{
...
"scripts": {
"electron": "concurrently \"react-scripts start\" \"wait-on http://localhost:3000 && electron .\"",
"build-win32": "react-scripts build && electron-builder --win --ia32",
"build-win64": "react-scripts build && electron-builder --win --x64",
"build-mac": "react-scripts build && electron-builder --mac",
}
...
}
运行打包
# 64位windows程序
yarn build-win64
运行完成以后在 dist
目录下会生成一个.exe安装文件
码云地址:https://gitee.com/charming-cheng/calculator.git