完成大体功能和样式

This commit is contained in:
2026-01-15 17:18:24 +08:00
parent 036eb3a206
commit 44a4b33502
211 changed files with 5480 additions and 7826 deletions

View File

@@ -0,0 +1,240 @@
<template>
<view class="calculator">
<customNavbar title="计算器" :showHome="true" />
<input class="display" 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 {
height: 120rpx;
font-size: 44rpx;
font-weight: 600;
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: 36rpx;
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: 40rpx;
}
.control {
background: #ff4d4f;
color: #fff;
box-shadow: 0 10rpx 20rpx rgba(255,77,79,0.4);
}
.equal {
grid-column: span 4;
background: linear-gradient(135deg, #4facfe, #00f2fe);
color: #fff;
font-size: 44rpx;
font-weight: 600;
box-shadow: 0 12rpx 28rpx rgba(79,172,254,0.5);
}
</style>