微信小程序开发实战:手把手教你从零撸一个高精度计算器(附完整源码)
微信小程序开发实战:从零构建高精度科学计算器
在移动互联网时代,微信小程序以其轻量级、即用即走的特性成为开发者展示技能的热门平台。而计算器作为基础工具类应用,看似简单却暗藏玄机——从界面布局到状态管理,从事件处理到浮点数精度控制,每一个环节都能考验开发者的基本功。本文将带你从零开始,打造一个支持连续运算、具备金融级计算精度的科学计算器小程序。
1. 项目初始化与基础配置
1.1 创建小程序项目
首先通过微信开发者工具创建新项目,选择"小程序"类型,填写AppID(或使用测试号),项目名称设为"PrecisionCalculator"。创建完成后,目录结构应包含:
├── pages/ │ └── index/ │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── utils/ ├── app.js ├── app.json ├── app.wxss └── project.config.json在app.json中配置导航栏样式:
{ "window": { "navigationBarTitleText": "高精度计算器", "navigationBarBackgroundColor": "#1E90FF", "navigationBarTextStyle": "white" } }1.2 基础页面结构搭建
修改index.wxml构建计算器骨架:
<view class="container"> <!-- 结果显示区域 --> <view class="display"> <view class="formula">{{formula}}</view> <view class="result">{{currentValue}}</view> </view> <!-- 按钮区域 --> <view class="keyboard"> <view class="row" wx:for="{{buttons}}" wx:key="index"> <button wx:for="{{item}}" wx:key="value" >// 精确加法 function accAdd(num1, num2) { const [int1, dec1 = ''] = num1.toString().split('.') const [int2, dec2 = ''] = num2.toString().split('.') const maxScale = Math.max(dec1.length, dec2.length) const scale = Math.pow(10, maxScale) return (num1 * scale + num2 * scale) / scale } // 精确乘法 function accMul(num1, num2) { let m = 0 const s1 = num1.toString() const s2 = num2.toString() try { m += s1.split('.')[1].length } catch(e) {} try { m += s2.split('.')[1].length } catch(e) {} return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m) } module.exports = { add: accAdd, sub: (a, b) => accAdd(a, -b), mul: accMul, div: (a, b) => accMul(a, 1/b) }2.2 状态管理模块
创建calculator.js管理计算状态:
const math = require('./math') class Calculator { constructor() { this.reset() } reset() { this.currentValue = '0' this.storedValue = null this.operator = null this.waitingForOperand = false this.formula = '' } inputDigit(digit) { if (this.waitingForOperand) { this.currentValue = digit this.waitingForOperand = false } else { this.currentValue = this.currentValue === '0' ? digit : this.currentValue + digit } } inputDecimal() { if (this.waitingForOperand) { this.currentValue = '0.' this.waitingForOperand = false return } if (!this.currentValue.includes('.')) { this.currentValue += '.' } } performOperation(nextOperator) { const inputValue = parseFloat(this.currentValue) if (this.storedValue === null) { this.storedValue = inputValue } else if (this.operator) { const currentValue = this.storedValue || 0 switch(this.operator) { case '+': this.storedValue = math.add(currentValue, inputValue); break case '-': this.storedValue = math.sub(currentValue, inputValue); break case '×': this.storedValue = math.mul(currentValue, inputValue); break case '÷': this.storedValue = math.div(currentValue, inputValue); break } } this.waitingForOperand = true this.operator = nextOperator this.formula = `${this.storedValue} ${this.operator || ''}` } } module.exports = Calculator3. 界面交互实现
3.1 按钮布局与样式
在index.wxss中定义计算器样式:
.container { height: 100vh; display: flex; flex-direction: column; background: #f5f5f5; } .display { flex: 2; padding: 20rpx; display: flex; flex-direction: column; justify-content: flex-end; align-items: flex-end; background: #2c3e50; } .formula { color: rgba(255,255,255,0.7); font-size: 36rpx; margin-bottom: 20rpx; } .result { color: white; font-size: 72rpx; } .keyboard { flex: 3; display: flex; flex-direction: column; background: #ecf0f1; } .row { flex: 1; display: flex; } .btn { flex: 1; margin: 5rpx; border-radius: 10rpx; font-size: 40rpx; display: flex; justify-content: center; align-items: center; background: white; } .operator { background: #f39c12; color: white; } .zero { flex: 2; }3.2 事件处理逻辑
在index.js中实现按钮交互:
const Calculator = require('../../utils/calculator') Page({ data: { currentValue: '0', formula: '', buttons: [ [ { label: 'C', value: 'clear', type: 'function' }, { label: '±', value: 'toggleSign', type: 'function' }, { label: '%', value: 'percentage', type: 'function' }, { label: '÷', value: '÷', type: 'operator' } ], [ { label: '7', value: '7', type: 'digit' }, { label: '8', value: '8', type: 'digit' }, { label: '9', value: '9', type: 'digit' }, { label: '×', value: '×', type: 'operator' } ], [ { label: '4', value: '4', type: 'digit' }, { label: '5', value: '5', type: 'digit' }, { label: '6', value: '6', type: 'digit' }, { label: '-', value: '-', type: 'operator' } ], [ { label: '1', value: '1', type: 'digit' }, { label: '2', value: '2', type: 'digit' }, { label: '3', value: '3', type: 'digit' }, { label: '+', value: '+', type: 'operator' } ], [ { label: '0', value: '0', type: 'digit' }, { label: '.', value: '.', type: 'decimal' }, { label: '⌫', value: 'backspace', type: 'function' }, { label: '=', value: '=', type: 'operator' } ] ] }, onLoad() { this.calculator = new Calculator() }, handleTap(e) { const { value, type } = e.currentTarget.dataset switch(type) { case 'digit': this.calculator.inputDigit(value) break case 'decimal': this.calculator.inputDecimal() break case 'operator': this.calculator.performOperation(value) break case 'function': this.handleFunction(value) break } this.updateDisplay() }, handleFunction(func) { switch(func) { case 'clear': this.calculator.reset() break case 'toggleSign': this.calculator.currentValue = String(-parseFloat(this.calculator.currentValue)) break case 'percentage': this.calculator.currentValue = String(parseFloat(this.calculator.currentValue) / 100) break case 'backspace': this.handleBackspace() break } }, handleBackspace() { const { currentValue } = this.calculator if (currentValue.length === 1 || (currentValue.length === 2 && currentValue.startsWith('-'))) { this.calculator.currentValue = '0' } else { this.calculator.currentValue = currentValue.slice(0, -1) } }, updateDisplay() { this.setData({ currentValue: this.calculator.currentValue, formula: this.calculator.formula }) } })4. 高级功能扩展
4.1 连续计算与历史记录
扩展calculator.js支持计算历史:
class EnhancedCalculator extends Calculator { constructor() { super() this.history = [] } performOperation(nextOperator) { // ...原有逻辑... if (nextOperator === '=') { this.history.push({ formula: `${this.storedValue} ${this.operator} ${this.currentValue}`, result: this.storedValue }) } } getHistory() { return this.history } }4.2 科学计算功能
添加科学计算函数:
// 在math.js中添加 function sqrt(num) { if (num < 0) throw new Error('负数不能开平方') let result = num const precision = 0.000001 while (Math.abs(result * result - num) > precision) { result = (result + num / result) / 2 } return result } // 在Calculator类中添加 handleScientific(func) { switch(func) { case 'sqrt': this.currentValue = String(math.sqrt(parseFloat(this.currentValue))) break case 'square': this.currentValue = String(math.mul(parseFloat(this.currentValue), parseFloat(this.currentValue))) break case 'reciprocal': this.currentValue = String(math.div(1, parseFloat(this.currentValue))) break } }4.3 主题切换功能
通过小程序全局样式变量实现主题切换:
// app.wxss :root { --primary-color: #1E90FF; --secondary-color: #FF8C00; --bg-color: #f5f5f5; --text-color: #333; } .dark-theme { --primary-color: #4A148C; --secondary-color: #FF6D00; --bg-color: #121212; --text-color: #f5f5f5; }然后在页面中动态切换类名:
toggleTheme() { const theme = this.data.theme === 'light' ? 'dark' : 'light' this.setData({ theme }) wx.setStorageSync('calculator_theme', theme) }