React + Electron + Antd 寫一個(gè)簡(jiǎn)單的計(jì)算器客戶端

創(chuàng)建react項(xiàng)目

create-react-app calculator

引入electron

yarn add electron --dev

引入antd

yarn add antd

引入electron-is-dev,用來判斷當(dāng)前是開發(fā)環(huán)境還是生產(chǎn)環(huán)境

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 ."
    }
  ...
}

運(yùn)行程序

# 運(yùn)行react
yarn start

# 運(yùn)行electron
yarn electron

每次都運(yùn)行兩個(gè)命令很麻煩,使用concurrently和wait-on,將兩個(gè)命令放在一起運(yùn)行

yarn add concurrently wait-on --dev

修改package.json

{
  ...
  "scripts": {
        "electron": "concurrently \"react-scripts start\" \"wait-on http://localhost:3000 && electron .\""
    }
  ...
}

客戶端渲染使用react

計(jì)算器一般分為兩塊技潘,一塊顯示計(jì)算結(jié)果感论,一塊為按鈕操作區(qū)枢泰,新建兩個(gè)組件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;
}

運(yùn)行項(xiàng)目可以看到客戶端

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",
    }
  ...
}

運(yùn)行打包

# 64位windows程序
yarn build-win64  

運(yùn)行完成以后在 dist目錄下會(huì)生成一個(gè).exe安裝文件

碼云地址:https://gitee.com/charming-cheng/calculator.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末产上,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子杂瘸,更是在濱河造成了極大的恐慌,老刑警劉巖碉咆,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖韩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡疫铜,警方通過查閱死者的電腦和手機(jī)茂浮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壳咕,“玉大人席揽,你說我怎么就攤上這事∥嚼澹” “怎么了幌羞?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)庞呕。 經(jīng)常有香客問我新翎,道長(zhǎng),這世上最難降的妖魔是什么住练? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮愁拭,結(jié)果婚禮上讲逛,老公的妹妹穿的比我還像新娘。我一直安慰自己岭埠,他們只是感情好盏混,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惜论,像睡著了一般许赃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馆类,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天混聊,我揣著相機(jī)與錄音,去河邊找鬼乾巧。 笑死句喜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沟于。 我是一名探鬼主播咳胃,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旷太!你這毒婦竟也來了展懈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎存崖,沒想到半個(gè)月后冻记,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡金句,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年檩赢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片违寞。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贞瞒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趁曼,到底是詐尸還是另有隱情军浆,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布挡闰,位于F島的核電站乒融,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏摄悯。R本人自食惡果不足惜赞季,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奢驯。 院中可真熱鬧申钩,春花似錦、人聲如沸瘪阁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽管跺。三九已至义黎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豁跑,已是汗流浹背廉涕。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贩绕,地道東北人火的。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像淑倾,于是被迫代替她去往敵國(guó)和親馏鹤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容