React + Electron + Antd 写一个简单的计算器客户端

创建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 (
      <TextArea
        type="text"
        id="content"
        autoSize={false}
        value={this.props.value}
        readOnly={true}
      />
    );
  }
}
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 (
      <Button
        type="button"
        className={this.props.className}
        value={this.props.value}
      >
        {this.props.value}
      </Button>
    );
  }
}
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 (
      <div id="main">
        <div id="input_text">
          <Text value={this.state.string} />
        </div>
        <div id="input_button" onClick={this.handleButton}>
          <Button value={1} />
          <Button value={2} />
          <Button value={3} />
          <Button value={"Back"} />
          <Button value={"C"} />
          <Button value={4} />
          <Button value={5} />
          <Button value={6} />
          <Button value={"+"} />
          <Button value={"-"} />
          <Button value={7} />
          <Button value={8} />
          <Button value={9} />
          <Button value={"*"} />
          <Button value={"/"} />
          <Button value={"附加"} />
          <Button value={0} />
          <Button value={"."} />
          <Button value={"="} className={"equal_size"} />
        </div>
      </div>
    );
  }
}

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(<App />, 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
image.png

打包

引入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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容