Files
RentWeAppFront/pages-biz/tool/calculator.vue
2026-01-30 09:01:38 +08:00

241 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="calculator">
<customNavbar title="计算器" :showHome="true" />
<textarea class="display" auto-height v-model="expression" disabled />
<view class="keys">
<view
v-for="item in keys"
:key="item"
class="key"
:class="{
operator: operators.includes(item) || item === '%',
equal: item === '=',
control: ['C','⌫'].includes(item)
}"
@click="press(item)"
@touchstart="onTouchStart(item)"
@touchend="onTouchEnd"
>
{{ item }}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
expression: '',
timer: null,
operators: ['+', '-', '*', '/'],
keys: [
'7','8','9','/',
'4','5','6','*',
'1','2','3','-',
'0','.','%','+',
'C','⌫','='
]
}
},
methods: {
press(key) {
const ops = this.operators
if (key === 'C') {
this.expression = ''
return
}
if (key === '⌫') {
return
}
if (key === '%') {
this.applyPercent()
return
}
if (key === '=') {
if (!this.expression) return
while (/[+\-*/.]$/.test(this.expression)) {
this.expression = this.expression.slice(0, -1)
}
if (!/^\d+(\.\d+)?([+\-*/]\d+(\.\d+)?)*$/.test(this.expression)) {
uni.showToast({ title: '格式错误', icon: 'none' })
return
}
this.expression = this.calculate(this.expression)
return
}
if (ops.includes(key)) {
if (!this.expression) return
if (ops.includes(this.expression.slice(-1))) {
this.expression =
this.expression.slice(0, -1) + key
return
}
}
if (key === '.') {
const last = this.expression.split(/[+\-*/]/).pop()
if (last.includes('.')) return
}
this.expression += key
},
/* 长按删除 */
onTouchStart(key) {
if (key !== '⌫') return
this.expression = this.expression.slice(0, -1)
this.timer = setInterval(() => {
if (!this.expression) {
clearInterval(this.timer)
return
}
this.expression = this.expression.slice(0, -1)
}, 120)
},
onTouchEnd() {
clearInterval(this.timer)
this.timer = null
},
/* 百分号处理(核心) */
applyPercent() {
if (!this.expression) return
const match = this.expression.match(/(\d+(\.\d+)?)([+\-*/])?(\d+(\.\d+)?)?$/)
if (!match) return
const base = parseFloat(match[1])
const op = match[3]
const value = match[4] ? parseFloat(match[4]) : null
let percentValue
if (!op) {
percentValue = base / 100
this.expression = percentValue.toString()
return
}
if (op === '+' || op === '-') {
percentValue = base * (value / 100)
} else {
percentValue = value / 100
}
this.expression =
this.expression.slice(0, match.index) +
base +
op +
percentValue
},
/* 计算(无 eval */
calculate(exp) {
const nums = exp.split(/[\+\-\*\/]/).map(Number)
const ops = exp.match(/[\+\-\*\/]/g) || []
for (let i = 0; i < ops.length; ) {
if (ops[i] === '*' || ops[i] === '/') {
const r =
ops[i] === '*'
? nums[i] * nums[i + 1]
: nums[i] / nums[i + 1]
nums.splice(i, 2, r)
ops.splice(i, 1)
} else {
i++
}
}
let result = nums[0]
for (let i = 0; i < ops.length; i++) {
result =
ops[i] === '+'
? result + nums[i + 1]
: result - nums[i + 1]
}
return Number(result.toFixed(10)).toString()
}
}
}
</script>
<style scoped>
.calculator {
padding: 30rpx;
padding-top: 175rpx;
background: #f7f8fa;
min-height: 100vh;
}
.display {
font-size: 70rpx;
font-weight: 600;
min-height: 400rpx;
width: 95%;
border-radius: 12rpx;
background: #fff;
padding: 0 24rpx;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.08);
}
.keys {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
}
.key {
background: #ffffff;
text-align: center;
padding: 36rpx 0;
font-size: 70rpx;
font-weight: 500;
border-radius: 16rpx;
box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.08);
}
.key:active {
transform: scale(0.95);
}
.operator {
background: #ffe8d9;
color: #ff7a18;
box-shadow: 0 10rpx 20rpx rgba(255,122,24,0.35);
font-size: 70rpx;
}
.control {
background: #ff4d4f;
color: #fff;
box-shadow: 0 10rpx 20rpx rgba(255,77,79,0.4);
}
.equal {
width: 215%;
background: linear-gradient(135deg, #4facfe, #00f2fe);
color: #fff;
font-size: 70rpx;
font-weight: 600;
box-shadow: 0 12rpx 28rpx rgba(79,172,254,0.5);
}
</style>