项目结构
代码
Index
- 处理显示格式
- 输入后判断合法性,存入expressions(expressions存表达式拆分数字和符号后的元素)
- 显示输入输出框、按键
import { CalSymbol, CommonConstants, Symbol, SymbolIcon } from '../common/constants/CommonConstants'
import CalculateUtil from '../common/util/CalculateUtil'
import CheckEmptyUtil from '../common/util/CheckEmptyUtil'
import { PressKeyItem } from '../ViewModel/PresskeyItem'
import keysModel from '../ViewModel/PresskeysViewModel'
@Entry
@Component
struct Index {
@State calVar:string=''
@State inputVar:string=''
@State expressions:string[]=[]
build() {
Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.End}) {
Input({inputVar:this.inputVar,calVar:this.calVar});
Keys({inputVar:$inputVar,calVar:$calVar,expressions:$expressions})
}
.width('100%')
.height('100%')
}
}
@Component
struct Input {
@Prop inputVar:string//输入框结果
@Prop calVar:string//输出框结果
build() {
Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.End}) {
TextInput({
text: this.inputVar
})
.fontColor('#ff515151')
.textAlign(TextAlign.End)
.fontSize((this.inputVar.length > CommonConstants.INPUT_LENGTH_LIM) ? $r('app.float.font_size_small') :
$r('app.float.font_size_big'))
.placeholderFont({ size: $r('app.float.font_size_big') })
.backgroundColor('#ffff')
.width('100%')
.height('40%')
.enabled(false)
Text(this.calVar)
.textAlign(TextAlign.End)
.width('100%')
.fontSize($r('app.float.font_size_big'))
.width('100%')
.height('60%')
}
.width('100%')
.height('30%')
}
}
@Component
struct Keys {
Number:PressKeyItem[]=keysModel.getNumberKeys()
Operation:PressKeyItem[]=keysModel.getOperationKey()
@Link calVar:string
@Link inputVar:string
@Link expressions:string[]
@Builder KeyItem(key:PressKeyItem,fontsize:Resource,fontcolor:Resource,bgcolor:Resource){
Text(key.icon)
.fontWeight(FontWeight.Bold)
.fontSize(fontsize)
.fontColor(fontcolor)
.borderRadius(10)
.backgroundColor(bgcolor)
.height('100%')
.width('100%')
.textAlign(TextAlign.Center)
.onClick(()=>{
if(key.flag===0){
this.inputSymbol(key.value)
} else {
this.inputNumber(key.value)
}
})
}
/**
* 输入符号
*/
inputSymbol(value:string){
if(CheckEmptyUtil.isEmpty(value))return;
let len=this.expressions.length
switch (value) {
case Symbol.CLEAN:
this.expressions=[]
this.calVar=''
this.inputVar=''
break;
case Symbol.DEL:
this.inputDel(len)
break;
case Symbol.EQU:
if(len===0)return
this.inputVar=this.calVar
this.calVar=''
this.expressions=[]
this.expressions.push(this.inputVar)
break;
default:
this.inputOperations(len,value)
break;
}
this.formatInputValue();
}
//输入回退键
inputDel(len:number){
/*console.log('Press Del')*/
if(len===0){
return
}
let last=this.expressions[len-1]
let lastLen=last.length
if(len>0){
if(lastLen===1){
this.expressions.pop()
}else{
this.expressions[len-1]=last.slice(0,lastLen-1)
}
}
if(this.expressions.length===0){//删除后长度变化 不能用len
this.inputVar=''
this.calVar=''
return
}
if(!CalculateUtil.isSymbol(this.expressions[len-1])){
this.getResult()//实时得到结果
}
}
//输入运算符
inputOperations(len:number,value:string){
if(CheckEmptyUtil.isEmpty(value))return;
// if(len<=0)return
let last = len > 0 ? this.expressions[len - 1] : undefined;
let secondLast = len > 1 ? this.expressions[len - 2] : undefined;
let symbol:string=this.getSymbol(value)
if(!last) {
if(value===Symbol.MIN){
this.expressions.push(symbol)
}
return
}
else{
if(!CalculateUtil.isSymbol(last))/*last是数字*/{
this.expressions.push(this.getSymbol(value))
return
}
else/*last是符号*/{
if (secondLast) {
if (!CalculateUtil.isSymbol(secondLast)) /*second是数字*/ {
if (!(value === Symbol.MIN)) {
this.expressions.pop()
this.expressions.push(symbol)
return
} else {
if (last === SymbolIcon.MUL || last === SymbolIcon.DIV) {
this.expressions.push(symbol)
return
} else {
this.expressions.pop()
this.expressions.push(symbol)
return
}
}
} else /*second是符号*/{
if (value === Symbol.ADD) {
this.expressions.pop()
return
} else if (value === Symbol.MIN) return
else {
this.expressions.slice(0, len - 2)
this.expressions.push(symbol)
}
}
}
}
}
}
getSymbol(value:string):string{
if(CheckEmptyUtil.isEmpty(value))return''
let symbol:string=''
switch(value){
case Symbol.ADD:
symbol=SymbolIcon.ADD
break;
case Symbol.MIN:
symbol=SymbolIcon.MIN
break;
case Symbol.MUL:
symbol=SymbolIcon.MUL
break;
case Symbol.DIV:
symbol=SymbolIcon.DIV
break;
default :
break;
}
return symbol
}
/**
* 输入数字
*/
inputNumber(value:string){
if(CheckEmptyUtil.isEmpty(value))return;
let len=this.expressions.length
let last = len > 0 ? this.expressions[len - 1] : ''
let secondLast = len > 1 ? this.expressions[len - 2] : undefined
if(!this.validateEnter(last,value))return
if(last===''){
this.expressions.push(value)
}else{
if(!secondLast)/*没有second*/{
this.expressions[len-1]+=value
}else{
if(CalculateUtil.isSymbol(secondLast))this.expressions[len-1]+=value
else if(CalculateUtil.isSymbol(last)) this.expressions.push(value)
}
}
if(this.formatInputValue) this.formatInputValue()
if (value !== CalSymbol.DOTS) {//实时得到计算结果
this.getResult();
}
}
//判断输入是否正确
validateEnter(last:string,value:string):boolean{
if(value===CalSymbol.PERCENT_SIGN) {
if (!last) return false//直接加%
if (CalculateUtil.isSymbol(last))return false//符号后加%
if(last.endsWith(CalSymbol.PERCENT_SIGN))return false
}
else if(value===CalSymbol.DOTS) {
//TODO 结果初始显示0
//TODO 只输入. 自动变成0.
if(last.endsWith(CalSymbol.PERCENT_SIGN))return false
if(last.indexOf('.')!==-1)return false//已经有小数点了
}else{
if(last==='0')return false
}
return true
}
/**
* 得到结果
*/
getResult(){
let calResult=CalculateUtil.parseExpression(this.deepCopy())//处理表达式逻辑得到的结果
console.log('getResult:'+calResult)
if(calResult==='NaN'){
this.calVar='Error'
return false//没得到结果
}else{
this.calVar=calResult
return true
}
}
/**
* 处理显示格式
*/
//复制expression
deepCopy():Array<string>{
let copyExpressions:Array<string>=Array.from(this.expressions)
return copyExpressions
}
//看华为官方给的案例代码,应该是千位后加逗号,但是在我的电脑上没实现
resultFormat(value: string) {
let reg = (value.indexOf('.') > -1) ? new RegExp("/(\d)(?=(\d{3})+\.)/g") : new RegExp("/(\d)(?=(?:\d{3})+$)/g");//正则表达式
return value.replace(reg, '$1,');//replace(patten,replacement) 字符串中patten换成后者
}
//输出处理后的表达式
formatInputValue() {
let deepExpressions: Array<string> = []
this.deepCopy().forEach((item:string,index:number)=>{
deepExpressions[index]=this.resultFormat(item)
})
this.inputVar=deepExpressions.join('')
}
build() {
Flex({ direction: FlexDirection.Row }) {
Grid() {
ForEach(
this.Number,
(key: PressKeyItem) => {
GridItem() {
if(key.icon&&key.flag==0) {//是操作符
this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.theme_color'), $r('app.color.key'))
}else{
this.KeyItem(key, $r('app.float.number_font_size'), $r('app.color.font_color'), $r('app.color.key'))
}
}
}, (key:PressKeyItem) => key.value)
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.columnsGap(1)
.rowsGap(1)
.width('75%')
.height('100%')
Grid() {
ForEach(
this.Operation,
(key: PressKeyItem) => {
GridItem() {
if(key.icon){
if (key.icon === '=') {
this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.font_color'),
$r('app.color.theme_color'))
} else {
this.KeyItem(key, $r('app.float.op_font_size'), $r('app.color.theme_color'),
$r('app.color.key'))
}
}
}
}, (key:PressKeyItem) => key.value)
}
.columnsTemplate('1fr')
.rowsTemplate('1fr 1fr 1fr 2fr')
.columnsGap(1)
.rowsGap(1)
.width('25%')
.height('100%')
.padding({ left: 1 })
}
.width('100%')
.height('57%')
.backgroundColor('#000')
}
}
CalculateUtil
- 处理expressions中的数字
- 完成计算
- 中序表达式变后序:
- 数字放队列前后顺序不变
- 符号放栈,和上一个符号(栈顶元素)比优先级,同级先计算/优先级高的放队列
- 最后都放到队列中
- 在队列中按顺序计算(数 数 符号)
- 中序表达式变后序:
import { CalSymbol, CommonConstants, OperationPriority, SymbolIcon } from '../constants/CommonConstants'
import CheckEmptyUtil from './CheckEmptyUtil'
class CalculateUtil{
/**
* 判断是否为字符 是返回true
*/
isSymbol(value:string):boolean{
return (CommonConstants.OPERATION.indexOf(value)!==-1)
}
/**
* 处理表达式
*/
parseExpression(expressions:string[]):string{
if(CheckEmptyUtil.isEmpty(expressions)){
return 'NaN'
}
let len=expressions.length
let outputStack:string[]=[]
let outputQueue:string[]=[]
//处理每一个元素
expressions.forEach((item:string,index:number)=>{
//处理百分号
if(item.indexOf(CalSymbol.PERCENT_SIGN)!==-1){
expressions[index]=this.mulOrDiv(item.replace('%',''),'100',SymbolIcon.DIV).toString()
}
//最后是运算符
if((index==len-1)&& this.isSymbol(item))expressions.pop()
})
while (expressions.length>0) {
let current:string|undefined=expressions.shift()
if(current!==undefined){
if(this.isSymbol(current)){//运算符
while(outputStack.length>0&&this.comparePriority(current,outputStack[outputStack.length-1])){
let popVar=outputStack.pop()
if(popVar!==undefined) outputQueue.push(popVar)
}
outputStack.push(current)
}else outputQueue.push(current)
}
}
while (outputStack.length>0){
let popVar=outputStack.pop()
if(popVar!==undefined) outputQueue.push(popVar)
}
return this.dealQueue(outputQueue)
}
dealQueue(queue:string[]):string{
if (CheckEmptyUtil.isEmpty(queue)) {
return 'NaN';
}
let outputStack:string[]=[]
while(queue.length>0){
let current:string|undefined=queue.shift();//一开始写成pop 注意shift和pop区别
if(current) {
if (!this.isSymbol(current)) {
outputStack.push(current)
}else{
let sec:string|undefined=outputStack.pop()
let fst:string|undefined=outputStack.pop()
if(sec!==undefined&&fst!==undefined){
let calResultVar=this.calResult(fst,sec,current)
outputStack.push(calResultVar)
}
}
}
}
if(outputStack.length!==1){
console.log('StopHere')
return 'NaN'
}
else{
let finResult:string=outputStack[0]?.endsWith(CalSymbol.DOTS)?
outputStack[0].substring(0, outputStack[0].length - 1) : outputStack[0];
return finResult
}
}
calResult(fst:string,sec:string,op:string):string{
if(CheckEmptyUtil.isEmpty(fst)||CheckEmptyUtil.isEmpty(sec)||CheckEmptyUtil.isEmpty(op)){
return 'NaN'
}
let result=0
switch (op) {
case SymbolIcon.ADD:case SymbolIcon.MIN:
result=this.addOrMin(fst,sec,op)
break;
case SymbolIcon.MUL:case SymbolIcon.DIV:
result=this.mulOrDiv(fst,sec,op)
break;
}
return this.numberToScientificNotation(result)
}
/**
* op1<=op2返回true,反之返回false
*
* @param op1,op2
*/
comparePriority(op1:string,op2:string):boolean{
if (CheckEmptyUtil.isEmpty(op1) || CheckEmptyUtil.isEmpty(op2)) {
return false;
}
return (OperationPriority[op1]<=OperationPriority[op2])
}
addOrMin(fst:string,sec:string,symbol:string){
let addFlag=(symbol===SymbolIcon.ADD)
if(this.containScientificNotation(fst)||this.containScientificNotation(sec)){
if(addFlag)return Number(fst)+Number(sec)
else return Number(fst)-Number(sec)
}
let leftLen:number=fst.split(CalSymbol.DOTS)[1]?fst.split(CalSymbol.DOTS)[1].length:0
let rightLen:number=sec.split(CalSymbol.DOTS)[1]?sec.split(CalSymbol.DOTS)[1].length:0
let maxLen:number=Math.max(leftLen,rightLen)
let multiple=Math.pow(10,maxLen)
if(addFlag){
return Number(((Number(fst)*multiple+Number(sec)*multiple)/multiple).toFixed(maxLen))
}else{
return Number(((Number(fst)*multiple-Number(sec)*multiple)/multiple).toFixed(maxLen))
}
}
/**
* 乘除
*/
mulOrDiv(fst:string,sec:string,symbol:string):number{
let mulFlag=(symbol===SymbolIcon.MUL)
if(this.containScientificNotation(fst)||this.containScientificNotation(sec)){
if(mulFlag)return Number(fst)*Number(sec)
else return Number(fst)/Number(sec)
}
let leftLen:number=fst.split(CalSymbol.DOTS)[1]?fst.split(CalSymbol.DOTS)[1].length:0
let rightLen:number=sec.split(CalSymbol.DOTS)[1]?sec.split(CalSymbol.DOTS)[1].length:0
if(mulFlag){
return Number(fst.replace(CalSymbol.DOTS,''))*Number(sec.replace(CalSymbol.DOTS,''))/Math.pow(10,leftLen+rightLen)
}else{
return (Number(fst.replace(CalSymbol.DOTS,''))/Number(sec.replace(CalSymbol.DOTS,'')))*Math.pow(10,rightLen-leftLen)
}
}
containScientificNotation(arg:string){
return(arg.indexOf('e')!==-1)
}
numberToScientificNotation(result: number):string{
if(result===Number.NEGATIVE_INFINITY||result===Number.POSITIVE_INFINITY)return 'NaN'
let resultStr:string=JSON.stringify(result)
if(this.containScientificNotation(resultStr))return resultStr
if(resultStr.replace(CalSymbol.DOTS,'').replace(SymbolIcon.MIN,'').length<=CommonConstants.NUM_MAX_LEN)return resultStr
let prefixNum=result>0?1:-1
result*=prefixNum;
let suffix=Math.floor(Math.log10(result))
let prefix=prefixNum*(result*Math.pow(10,-suffix))
return prefix.toFixed(2).toString()+'e'+suffix.toString()
}
}
export default new CalculateUtil()
CheckEmptyUtil
(我为什么要另开一个文件放判断置空的方法
class CheckEmptyUtil{
/**
* 空返回true
*/
isEmpty(obj:object|string):boolean{
return (obj===undefined||obj===null||obj==='')
}
}
export default new CheckEmptyUtil();
PresskeyItem
按键类:类型flag,代表值:value,显示的符号:icon
export class PressKeyItem{
flag:number
value:string
icon:string
constructor(flag:number,value:string,icon?:string) {
this.flag=flag
this.value=value
if(flag==0&&icon){
this.icon=icon
}else{
this.icon=value
}
}
}
PresskeysViewModel
创建按键数组
import { PressKeyItem } from './PresskeyItem';
export class PressKeyViewModel{
getNumberKeys():Array<PressKeyItem>{
return[
new PressKeyItem(0,'clean','C'),
new PressKeyItem(0,'div','÷'),
new PressKeyItem(0,'mul','×'),
new PressKeyItem(1,'7'),
new PressKeyItem(1,'8'),
new PressKeyItem(1,'9'),
new PressKeyItem(1,'4'),
new PressKeyItem(1,'5'),
new PressKeyItem(1,'6'),
new PressKeyItem(1,'1'),
new PressKeyItem(1,'2'),
new PressKeyItem(1,'3'),
new PressKeyItem(1,'%'),
new PressKeyItem(1,'0'),
new PressKeyItem(1,'.'),
]
}
getOperationKey():Array<PressKeyItem>{
return [
new PressKeyItem(0,'del','◀'),
new PressKeyItem(0,'min','-'),
new PressKeyItem(0,'add','+'),
new PressKeyItem(0,'equ','='),
]
}
}
let keysModel:PressKeyViewModel = new PressKeyViewModel();
export default keysModel as PressKeyViewModel;
CommonConstants
常量
export class CommonConstants {
static readonly INPUT_LENGTH_LIM:number=9
static readonly OPERATION:string='+-×÷'
static readonly E:string='e'
static readonly NUM_MAX_LEN:number=16
}
export enum Symbol {
ADD = 'add',
MIN = 'min',
MUL = 'mul',
DIV = 'div',
CLEAN = 'clean',
DEL = 'del',
EQU = 'equ'
}
export enum SymbolIcon{
ADD = '+',
MIN = '-',
MUL = '×',
DIV = '÷',
}
export enum CalSymbol{
PERCENT_SIGN='%',
DOTS='.'
}
type Priority=Record<string,number>
export const OperationPriority:Priority={
/*SymbolIcon.ADD=1,*/
'-':1,
'+':1,
'×':2,
'÷':2
}