自己开发封装的微信小程序计算器组件,复制即可用(完整代码)

    技术2022-07-10  139

    写在前面

    我在开发自己用的账本类微信小程序时,因为嫌老是要切屏打开计算器,算完之后还要照着计算结果手动记录很不方便,尤其是当计算出来的结果位数较长时,容易看错或打错,因此想自己开发封装一个计算器组件在需要用的页面里,这样不仅可以减少切屏的次数,还可以直接去拿到计算结果自动记录,非常的方便。

    按惯例先贴效果再上代码,啰嗦的话后面再讲。

    效果预览

    完整代码

    先贴目录,自己对应下面的代码复制到哪个对应的文件里(后续等我把自己的项目整理差不多了,有空写一个demo放到github上,这样下载起来会更方便一些。现在就先复制吧):

    // component/calculator/calculator.js var util = require("../../utils/util.js"); Component({ /** * 组件的属性列表 */ properties: { }, /** * 页面的初始数据 */ data: { pageHeight: wx.getSystemInfoSync().windowHeight,//页面高度 finalResult: 0,//显示屏上的结果 operationBtnsOn: false,//是否按下了操作按钮 preResult: 0,//上次的结果 operationBtnType: '',//操作 savePre: false,//是否保存上一次的显示屏内容 clearResult: false,//是否清除显示屏上的结果 resultText: "",//输入记录 historyOperationList: [],//历史操作列表 figures: 8,//保留位数 }, methods: { /** * 点击清空 */ cleanResult: function () { this.setData({ finalResult: 0, operationBtnsOn: false, operationBtnType: '', preResult: 0, savePre: false, clearResult: false, resultText: '' }) }, /** * 点击退格 */ deleteInput: function () { if (this.data.finalResult == "" || this.data.finalResult == "-") { this.setData({ finalResult: 0 }) } if (typeof this.data.finalResult == "number") { this.data.finalResult = this.data.finalResult.toString(); } if (this.data.resultText != "" && Number(this.data.finalResult) != 0) { let temp = this.data.resultText.substring(this.data.resultText.length - 1); if (temp == "+" || (temp == "-" && this.data.resultText) || temp == "*" || temp == "/") { this.setData({ resultText: this.data.resultText + this.data.finalResult.substring(0, this.data.finalResult.length - 1) }) } else { this.setData({ resultText: this.data.resultText.substring(0, this.data.resultText.length - 1) }) } } else { if (this.data.finalResult.substring(0, this.data.finalResult.length - 1).length > 0) { this.setData({ resultText: this.data.resultText.substring(0, this.data.resultText.length - 1) }) } else { if (this.data.resultText.length > 0) { this.setData({ resultText: this.data.resultText.substring(0, this.data.resultText.length - 1) }) } } } if (Number(this.data.finalResult) != 0 && Number(this.data.finalResult.substring(0, this.data.finalResult.length - 1)) != 0) { this.setData({ finalResult: this.data.finalResult.substring(0, this.data.finalResult.length - 1) }) } else { if (this.data.finalResult.substring(0, this.data.finalResult.length - 1).length > 1) { this.setData({ finalResult: this.data.finalResult.substring(0, this.data.finalResult.length - 1) }) } else { this.setData({ finalResult: 0 }) } } }, /** * 点击输入 */ inputNum: function (e) { var finalResult = this.data.finalResult; if (Number(finalResult) == 0) { if (finalResult.length > 8) { return; } if (e.currentTarget.dataset.num == ".") { if (finalResult === "-0") { finalResult = "-0." } else { finalResult = "0." } } else { if (finalResult.toString().substring(0, 2) == "0." && finalResult.length >= 2) { finalResult += e.currentTarget.dataset.num; } else { if (finalResult === "-0.") { finalResult = "-0." + e.currentTarget.dataset.num; } else { if (finalResult === "-0") { finalResult = "-" + e.currentTarget.dataset.num; } else { finalResult = e.currentTarget.dataset.num; } } } } this.setData({ savePre: false }) } else { if (this.data.savePre) { if (finalResult.length > 8 && !this.data.operationBtnsOn) { return; } if (e.currentTarget.dataset.num == ".") { if (finalResult == "-") { finalResult = "-0."; } else { finalResult = "0."; } } else { if (finalResult == "-") { finalResult = "-" + e.currentTarget.dataset.num; } else { finalResult = e.currentTarget.dataset.num; } } this.setData({ savePre: false }) } else { if (finalResult.length > 8) { return; } if (this.data.clearResult) { finalResult = e.currentTarget.dataset.num; this.setData({ clearResult: false }) } else { if (finalResult == ".") { finalResult = "0." + e.currentTarget.dataset.num; } else { if (finalResult.indexOf(".") > -1) { if (e.currentTarget.dataset.num != ".") { finalResult = finalResult + e.currentTarget.dataset.num; } } else { finalResult = finalResult + e.currentTarget.dataset.num; } } } } } this.setData({ finalResult: finalResult, resultText: Number(this.data.resultText) == 0 ? e.currentTarget.dataset.num : this.data.resultText + e.currentTarget.dataset.num }) }, /** * 点击相加 */ operationPlus: function () { if (this.data.operationBtnsOn) { this.handleOperation(); this.setData({ operationBtnType: 'plus', savePre: true, clearResult: false }) } else { this.setData({ operationBtnsOn: true, operationBtnType: 'plus', preResult: this.data.finalResult, savePre: true, clearResult: false }) } this.setData({ resultText: this.data.resultText + "+" }) }, /** * 点击相减 */ operationMinus: function () { if (this.data.operationBtnsOn) { this.handleOperation(); this.setData({ operationBtnType: 'minus', savePre: true, clearResult: false }) } else { this.setData({ operationBtnsOn: true, operationBtnType: 'minus', preResult: this.data.finalResult, savePre: true, clearResult: false }) } this.setData({ resultText: this.data.resultText + "-" }) }, /** * 点击相乘 */ operationMultiply: function () { if (this.data.operationBtnsOn) { this.handleOperation(); this.setData({ operationBtnType: 'multiply', savePre: true, clearResult: false }) } else { this.setData({ operationBtnsOn: true, operationBtnType: 'multiply', preResult: this.data.finalResult, savePre: true, clearResult: false }) } this.setData({ resultText: this.data.resultText + "x" }) }, /** * 点击相除 */ operationDivide: function () { if (this.data.operationBtnsOn) { this.handleOperation(); this.setData({ operationBtnType: 'divide', savePre: true, clearResult: false }) } else { this.setData({ operationBtnsOn: true, operationBtnType: 'divide', preResult: this.data.finalResult, savePre: true, clearResult: false }) } this.setData({ resultText: this.data.resultText + "÷" }) }, /** * 处理连续计算 */ handleOperation() { if (this.data.operationBtnType == 'plus') { this.setData({ finalResult: util.add(this.data.preResult, this.data.finalResult) }) this.setData({ preResult: this.data.finalResult, }) } if (this.data.operationBtnType == 'minus') { this.setData({ finalResult: util.sub(this.data.preResult, this.data.finalResult) }) this.setData({ preResult: this.data.finalResult, }) } if (this.data.operationBtnType == 'multiply') { this.setData({ finalResult: util.mul(this.data.preResult, this.data.finalResult) }) this.setData({ preResult: this.data.finalResult, }) } if (this.data.operationBtnType == 'divide') { this.setData({ finalResult: util.div(this.data.preResult, this.data.finalResult, this.data.figures) }) this.setData({ preResult: this.data.finalResult, }) } }, /** * 点击等于号 */ operationEquals: function () { if (this.data.operationBtnsOn) { if (this.data.operationBtnType == 'plus') { this.setData({ operationBtnsOn: false, clearResult: true, savePre: false, finalResult: util.add(this.data.preResult, this.data.finalResult) }) } else if (this.data.operationBtnType == 'minus') { this.setData({ operationBtnsOn: false, clearResult: true, savePre: false, finalResult: util.sub(this.data.preResult, this.data.finalResult) }) } else if (this.data.operationBtnType == 'multiply') { this.setData({ operationBtnsOn: false, clearResult: true, savePre: false, finalResult: util.mul(this.data.preResult, this.data.finalResult) }) } else if (this.data.operationBtnType == 'divide') { this.setData({ operationBtnsOn: false, clearResult: true, savePre: false, finalResult: util.div(this.data.preResult, this.data.finalResult, this.data.figures) }) } } if (this.data.resultText) { var resultText = this.data.resultText + "=" + this.data.finalResult; this.data.historyOperationList.push(resultText); this.setData({ resultText: '' }) } this.showHistory(); }, /** * 打印历史操作 */ showHistory: function () { console.log(this.data.historyOperationList) }, //点击切换正负 opposite: function () { if (this.data.savePre) { if (this.data.finalResult === "-0") { this.setData({ finalResult: 0 }) } else { this.setData({ finalResult: "-0" }) } } else { if (this.data.finalResult === 0) { this.setData({ finalResult: "-0" }) } else { this.setData({ finalResult: -this.data.finalResult }) } } } } }) /* component/calculator/calculator.wxss */ .tools_calculator{ background-color: #333; padding: 0 32rpx; } .calculator_viewArea{ min-height: 125px; width: 100%; position: relative; } .calculator_viewArea_result{ font-size: 100rpx; color: #fff; text-align: end; position: absolute; bottom: 0; right: 20rpx; word-break: break-all; } .calculator_operationArea{ display: flex; flex-direction: column; } .calculator_operationArea_row{ display: flex; flex-direction: row; } .calculator_operationArea_eachBox{ width: 150rpx; height: 150rpx; border-radius: 100rpx; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #fff; margin: 5px; font-size: 60rpx; } .greyBox{ background-color: #999; } .blackBox{ background-color: #666; } .orangeBox{ background-color: orange; } .isActive{ color: orange; background-color: #fff; } <!-- component/calculator/calculator.wxml --> <view class="tools_calculator" style="height:{{pageHeight}}px"> <view class="calculator_viewArea"> <view class="calculator_viewArea_result">{{finalResult==null?"错误":finalResult}}</view> </view> <view class="calculator_operationArea"> <view class="calculator_operationArea_row"> <view class="calculator_operationArea_eachBox greyBox" bind:tap="cleanResult">C</view> <view class="calculator_operationArea_eachBox greyBox" bind:tap="deleteInput"></view> <view class="calculator_operationArea_eachBox greyBox" bind:tap="opposite">±</view> <view class="calculator_operationArea_eachBox orangeBox {{savePre&&operationBtnType=='divide'?'isActive':''}}" bind:tap="operationDivide">÷</view> </view> <view class="calculator_operationArea_row"> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="7">7</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="8">8</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="9">9</view> <view class="calculator_operationArea_eachBox orangeBox {{savePre&&operationBtnType=='multiply'?'isActive':''}}" bind:tap="operationMultiply">x</view> </view> <view class="calculator_operationArea_row"> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="4">4</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="5">5</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="6">6</view> <view class="calculator_operationArea_eachBox orangeBox {{savePre&&operationBtnType=='minus'?'isActive':''}}" bind:tap="operationMinus">-</view> </view> <view class="calculator_operationArea_row"> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="1">1</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="2">2</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num="3">3</view> <view class="calculator_operationArea_eachBox orangeBox {{savePre&&operationBtnType=='plus'?'isActive':''}}" bind:tap="operationPlus">+</view> </view> <view class="calculator_operationArea_row"> <view class="calculator_operationArea_eachBox blackBox" style="width:320rpx;" bind:tap="inputNum" data-num="0">0</view> <view class="calculator_operationArea_eachBox blackBox" bind:tap="inputNum" data-num=".">.</view> <view class="calculator_operationArea_eachBox orangeBox" bind:tap="operationEquals">=</view> </view> </view> </view>

    “component/calculator/calculator.json”文件:

    { "component": true }

    “utils/utils.js”文件:

    /* 函数,加法函数,用来得到精确的加法结果 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数) 返回值:两数相加的结果 */ function add(arg1, arg2) { arg1 = arg1.toString(), arg2 = arg2.toString(); var arg1Arr = arg1.split("."), arg2Arr = arg2.split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""; var maxLen = Math.max(d1.length, d2.length); var m = Math.pow(10, maxLen); var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen)); var d = arguments[2]; return typeof d === "number" ? Number((result).toFixed(d)) : result; } /* 函数:减法函数,用来得到精确的减法结果 说明:函数返回较为精确的减法结果。 参数:arg1:第一个加数;arg2第二个加数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数) 返回值:两数相减的结果 */ function sub(arg1, arg2) { return this.add(arg1, -Number(arg2), arguments[2]); } /* 函数:乘法函数,用来得到精确的乘法结果 说明:函数返回较为精确的乘法结果。 参数:arg1:第一个乘数;arg2第二个乘数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数) 返回值:两数相乘的结果 */ function mul(arg1, arg2) { var r1 = arg1.toString(), r2 = arg2.toString(), m, resultVal, d = arguments[2]; m = (r1.split(".")[1] ? r1.split(".")[1].length : 0) + (r2.split(".")[1] ? r2.split(".")[1].length : 0); resultVal = Number(r1.replace(".", "")) * Number(r2.replace(".", "")) / Math.pow(10, m); return typeof d !== "number" ? Number(resultVal) : Number(resultVal.toFixed(parseInt(d))); } /* 函数:除法函数,用来得到精确的除法结果 说明:函数返回较为精确的除法结果。 参数:arg1:除数;arg2被除数;d要保留的小数位数(可以不传此参数,如果不传则不处理小数位数) 返回值:arg1除于arg2的结果 */ function div(arg1, arg2, d) { var r1 = arg1.toString(), r2 = arg2.toString(), m, resultVal; m = (r2.split(".")[1] ? r2.split(".")[1].length : 0) - (r1.split(".")[1] ? r1.split(".")[1].length : 0); resultVal = Number(r1.replace(".", "")) / Number(r2.replace(".", "")) * Math.pow(10, m); return typeof d !== "number" ? Number(resultVal) : Number(resultVal.toFixed(parseInt(d))); } module.exports = { add: add, sub: sub, mul: mul, div: div }

    下面是要调用的页面的写法:

    <!--pages/test/test.wxml--> <calculator></calculator>

    “pages/test/test.json”文件:

    { "usingComponents": { "calculator": "/component/calculator/calculator" } }

    小结

    调用封装的组件应该都会吧,不会的话上面代码也贴了,就是要用的页面比如上文的test页面的json文件里引一下,wxml文件里调一下。

    utils里的方法是为了处理js在进行运算时如果有小数会出现精度丢失导致结果不准确的问题的。

    很明显可以从界面看出来,我这个是仿ios的计算器写的,所以一些处理也和它类似。我自己在测试的时候是用自己手机先算一遍,再用我写的计算器组件算一遍,去对照结果的,暂时没发现什么问题。

    简单说一下思路:就是先点击数字键,相当于输入要参与运算的第一个数字,然后按操作符,这个时候用一个变量记录你已经用了运算符了,那接下来再按数字键就是输入要参与运算的第二个数字了。然后第二个数字输完按等于号计算结果,如果要连续计算,那再按运算符的时候也要把当前结果计算出来。

    就差不多这样,思路很简单,但实际处理起来,小数点,正负数,特别是对于“-0,0.xxx”这种的要考虑清楚,还是有很多坑在里面的。

    其他暂时没想到什么要注意的。有问题大家一起探讨。

    可以优化的点(不影响使用)

    1、ios的计算器,不管是输入还是计算结果,都严格控制在一行显示的。我这个呢是输入一行以内,计算结果两行以内。反正不影响使用,我也和ios一样都是输入9位数以内运算,无非就是难看一些。如果你觉得难看呢可以自己改一下,也像ios那样一行显示,确实更好看,而且人家位数多了还改变了字体大小,这个效果呢倒是很好实现,根据屏幕上显示的结果长度去动态改变css就行,写几个不同字体大小的class,去切换,但我想我这个反正是两行了,也没必要搞这个字体大小了,就没弄,想搞的可以弄一下,很简单也更好看。

    2、我只进行了除法运算时小数点最多保留到8位的限制,加减乘的小数位没做限制,加减乘除的整数位也没做限制。这个和上面那个控制到一行有关,你要是全部控制位数了那肯定就不会超出一行了,但我想想没必要吧,就处理一下无限小数就行了呗,不搞那么麻烦。还是那句话,根据自己需要来。

    3、可以看到我的代码里记录了历史操作,也专门写了个方法,但里面只有一个console。这个本来是要做个按钮专门把历史操作反映到界面上的,但因为这个记录啊还有点问题,我就只在按“=”的时候调用一下console,反正用户端也看不出什么,后续再优化什么的都行。

    那这个记录有什么问题呢?其实计算结果的记录,正常的加减乘除操作记录都没什么问题,有问题的是:处理正负号,多次按小数点,想打“0.xxx”的时候只打“.xxx”(这个很多人都有吧),各种情况下按退格键这些情况下,会记录每个按下的小数点啦,少记录一个0直接记录.和后面的数字啦等等。但这个地方吧,因为你不能影响到正常操作的记录,也不好判断值也不好判断长度,所以怎么去限制我输入一个小数的时候多次点击小数点,在记录中只记录一个正确位置的小数点就是问题了。还有就是我想输入“0.xxx”,但我先狂按“00000000”然后在输入“0.xx”,那我记录里应该只有“0.xx”,前面那么多无效的0不能记录。

    不过呢,我这个记录是每个按键都去记录的。对我这个来说,不能只把记录写在运算符按键上,不记录数字键,然后每次操作运算符的时候记录当前屏幕显示结果。因为此时当前屏幕显示结果已经是运算完的结果了,记录的不是第二个数。这是由于我只用了一个字段去保存计算结果而不保存参与运算的数。如果我用两个字段分别保存参与运算的第一个数和第二个数,然后再用一个字段专门去存运算结果,那这样我只在操作运算符的时候去记录应该就是可行的了。

    但这样要改的地方比较多,只能说当初构思的时候还是想的不完善,还有很多要注意的地方。 不需要操作记录的直接把相关代码删掉就行了,这样还能给这个组件瘦瘦身。

    4、最后也是最值得优化的地方:代码写的不够简洁漂亮。可以看到我对小数点和负号的处理很粗糙,部分逻辑可能存在冗余。也不知道有没有潜在的bug。目前测试下来暂时没发现什么问题,等待后续再看。

    Processed: 0.010, SQL: 9