2026-01-15 17:18:24 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="calculator">
|
|
|
|
|
|
<customNavbar title="计算器" :showHome="true" />
|
|
|
|
|
|
|
2026-01-30 09:01:38 +08:00
|
|
|
|
<textarea class="display" auto-height v-model="expression" disabled />
|
2026-01-15 17:18:24 +08:00
|
|
|
|
|
|
|
|
|
|
<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','.','%','+',
|
2026-01-30 09:01:38 +08:00
|
|
|
|
'C','⌫','='
|
2026-01-15 17:18:24 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
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 {
|
2026-01-30 09:01:38 +08:00
|
|
|
|
font-size: 70rpx;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
font-weight: 600;
|
2026-01-30 09:01:38 +08:00
|
|
|
|
min-height: 400rpx;
|
|
|
|
|
|
width: 95%;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
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;
|
2026-01-30 09:01:38 +08:00
|
|
|
|
font-size: 70rpx;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
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);
|
2026-01-30 09:01:38 +08:00
|
|
|
|
font-size: 70rpx;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control {
|
|
|
|
|
|
background: #ff4d4f;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
box-shadow: 0 10rpx 20rpx rgba(255,77,79,0.4);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.equal {
|
2026-01-30 09:01:38 +08:00
|
|
|
|
width: 215%;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
|
|
|
|
|
color: #fff;
|
2026-01-30 09:01:38 +08:00
|
|
|
|
font-size: 70rpx;
|
2026-01-15 17:18:24 +08:00
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
box-shadow: 0 12rpx 28rpx rgba(79,172,254,0.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|