写在前面
我在开发自己用的账本类微信小程序时,因为嫌老是要切屏打开计算器,算完之后还要照着计算结果手动记录很不方便,尤其是当计算出来的结果位数较长时,容易看错或打错,因此想自己开发封装一个计算器组件在需要用的页面里,这样不仅可以减少切屏的次数,还可以直接去拿到计算结果自动记录,非常的方便。
按惯例先贴效果再上代码,啰嗦的话后面再讲。
效果预览
完整代码
先贴目录,自己对应下面的代码复制到哪个对应的文件里(后续等我把自己的项目整理差不多了,有空写一个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。目前测试下来暂时没发现什么问题,等待后续再看。