346 lines
8.7 KiB
TypeScript
346 lines
8.7 KiB
TypeScript
// pages/calculator/calculator.ts
|
||
Page({
|
||
data: {
|
||
displayValue: '0',
|
||
history: '',
|
||
operator: null as string | null,
|
||
firstOperand: null as number | null,
|
||
waitingForSecondOperand: false,
|
||
isScientific: false,
|
||
showHistory: false,
|
||
showHelp: false,
|
||
showSettingsMenu: false,
|
||
historyList: [] as Array<{expression: string, result: string}>
|
||
},
|
||
|
||
onShareAppMessage() {
|
||
return {
|
||
title: '便捷好用的科学计算器',
|
||
path: '/pages/calculator/calculator'
|
||
}
|
||
},
|
||
|
||
onShareTimeline() {
|
||
return {
|
||
title: '便捷好用的科学计算器'
|
||
}
|
||
},
|
||
|
||
toggleHelp() {
|
||
this.setData({
|
||
showHelp: !this.data.showHelp,
|
||
showSettingsMenu: false
|
||
});
|
||
},
|
||
|
||
toggleSettings() {
|
||
this.vibrate();
|
||
this.setData({ showSettingsMenu: !this.data.showSettingsMenu });
|
||
},
|
||
|
||
closeSettings() {
|
||
if (this.data.showSettingsMenu) {
|
||
this.setData({ showSettingsMenu: false });
|
||
}
|
||
},
|
||
|
||
onLoad() {
|
||
// 从本地存储恢复历史记录
|
||
const saved = wx.getStorageSync('CALC_HISTORY');
|
||
if (saved && Array.isArray(saved)) {
|
||
this.setData({ historyList: saved });
|
||
}
|
||
},
|
||
|
||
vibrate() {
|
||
wx.vibrateShort({ type: 'light' }).catch(() => {});
|
||
},
|
||
|
||
preventTouchMove() {
|
||
// 阻止历史面板底层滚动
|
||
},
|
||
|
||
toggleMode() {
|
||
this.vibrate();
|
||
this.setData({
|
||
isScientific: !this.data.isScientific
|
||
});
|
||
},
|
||
|
||
switchMode(e: any) {
|
||
const mode = e.currentTarget.dataset.mode;
|
||
const isSci = (mode === 'scientific');
|
||
if (this.data.isScientific !== isSci) {
|
||
this.vibrate();
|
||
this.setData({ isScientific: isSci });
|
||
}
|
||
},
|
||
|
||
onDigit(e: any) {
|
||
this.vibrate();
|
||
const digit = e.currentTarget.dataset.digit;
|
||
const { displayValue, waitingForSecondOperand } = this.data;
|
||
|
||
// 科学模式允许更多数字
|
||
const limit = 15;
|
||
if (!waitingForSecondOperand && displayValue.length >= limit) {
|
||
return;
|
||
}
|
||
|
||
if (waitingForSecondOperand) {
|
||
this.setData({
|
||
displayValue: digit,
|
||
waitingForSecondOperand: false
|
||
});
|
||
} else {
|
||
this.setData({
|
||
displayValue: displayValue === '0' ? digit : displayValue + digit
|
||
});
|
||
}
|
||
},
|
||
|
||
onDot() {
|
||
this.vibrate();
|
||
const { displayValue, waitingForSecondOperand } = this.data;
|
||
if (waitingForSecondOperand) {
|
||
this.setData({
|
||
displayValue: '0.',
|
||
waitingForSecondOperand: false
|
||
});
|
||
} else if (displayValue.indexOf('.') === -1) {
|
||
this.setData({
|
||
displayValue: displayValue + '.'
|
||
});
|
||
}
|
||
},
|
||
|
||
onClear() {
|
||
this.vibrate();
|
||
this.setData({
|
||
displayValue: '0',
|
||
history: '',
|
||
operator: null,
|
||
firstOperand: null,
|
||
waitingForSecondOperand: false
|
||
});
|
||
},
|
||
|
||
onDelete() {
|
||
this.vibrate();
|
||
const { displayValue } = this.data;
|
||
if (displayValue.length > 1) {
|
||
this.setData({
|
||
displayValue: displayValue.slice(0, -1)
|
||
});
|
||
} else {
|
||
this.setData({ displayValue: '0' });
|
||
}
|
||
},
|
||
|
||
onToggleSign() {
|
||
this.vibrate();
|
||
const { displayValue } = this.data;
|
||
const newValue = parseFloat(displayValue) * -1;
|
||
this.setData({
|
||
displayValue: String(newValue)
|
||
});
|
||
},
|
||
|
||
onConstant(e: any) {
|
||
this.vibrate();
|
||
const type = e.currentTarget.dataset.const;
|
||
let val = 0;
|
||
if (type === 'pi') val = Math.PI;
|
||
if (type === 'e') val = Math.E;
|
||
|
||
this.setData({
|
||
displayValue: String(val).slice(0, 10),
|
||
waitingForSecondOperand: true // 下次输入将开启新数字,或者作为操作数
|
||
});
|
||
},
|
||
|
||
onMathFunc(e: any) { // 单操作数立即计算
|
||
this.vibrate();
|
||
const func = e.currentTarget.dataset.func;
|
||
let val = parseFloat(this.data.displayValue);
|
||
let result = 0;
|
||
let desc = '';
|
||
|
||
switch(func) {
|
||
case 'sin':
|
||
// 默认转为弧度计算 (度数 * PI / 180)
|
||
result = Math.sin(val * Math.PI / 180);
|
||
desc = `sin(${val})`;
|
||
break;
|
||
case 'cos':
|
||
result = Math.cos(val * Math.PI / 180);
|
||
desc = `cos(${val})`;
|
||
break;
|
||
case 'tan':
|
||
result = Math.tan(val * Math.PI / 180);
|
||
desc = `tan(${val})`;
|
||
break;
|
||
case 'log':
|
||
result = Math.log10(val);
|
||
desc = `log(${val})`;
|
||
break;
|
||
case 'ln':
|
||
result = Math.log(val);
|
||
desc = `ln(${val})`;
|
||
break;
|
||
case 'sqrt':
|
||
result = Math.sqrt(val);
|
||
desc = `√(${val})`;
|
||
break;
|
||
case 'pow2':
|
||
result = Math.pow(val, 2);
|
||
desc = `sqr(${val})`;
|
||
break;
|
||
case 'percent':
|
||
result = val / 100;
|
||
desc = `${val}%`;
|
||
break;
|
||
}
|
||
|
||
// 格式化结果
|
||
const formatted = parseFloat(result.toPrecision(12));
|
||
|
||
this.setData({
|
||
displayValue: String(formatted),
|
||
history: desc,
|
||
waitingForSecondOperand: true // 计算完后,下次输入数字是新数字
|
||
});
|
||
|
||
this.addToHistory(desc, String(formatted));
|
||
},
|
||
|
||
onOperator(e: any) {
|
||
this.vibrate();
|
||
const nextOperator = e.currentTarget.dataset.op;
|
||
const { displayValue, operator, firstOperand } = this.data;
|
||
const inputValue = parseFloat(displayValue);
|
||
|
||
if (operator && this.data.waitingForSecondOperand) {
|
||
this.setData({
|
||
operator: nextOperator,
|
||
// 更新历史记录中的符号
|
||
history: `${firstOperand} ${nextOperator}`
|
||
});
|
||
return;
|
||
}
|
||
|
||
let newFirstOperand = firstOperand;
|
||
|
||
if (firstOperand == null) {
|
||
newFirstOperand = inputValue;
|
||
} else if (operator) {
|
||
const result = this.performCalculation(operator, firstOperand, inputValue);
|
||
newFirstOperand = result;
|
||
this.setData({
|
||
displayValue: String(result),
|
||
});
|
||
}
|
||
|
||
this.setData({
|
||
firstOperand: newFirstOperand,
|
||
waitingForSecondOperand: true,
|
||
operator: nextOperator,
|
||
history: `${newFirstOperand} ${nextOperator}`
|
||
});
|
||
},
|
||
|
||
onEqual() {
|
||
this.vibrate();
|
||
const { displayValue, operator, firstOperand } = this.data;
|
||
const inputValue = parseFloat(displayValue);
|
||
|
||
if (operator && firstOperand != null) {
|
||
const result = this.performCalculation(operator, firstOperand, inputValue);
|
||
const expression = `${firstOperand} ${operator} ${inputValue}`;
|
||
|
||
this.setData({
|
||
displayValue: String(result),
|
||
firstOperand: null,
|
||
operator: null,
|
||
waitingForSecondOperand: true,
|
||
history: `${firstOperand} ${operator} ${inputValue} =`
|
||
});
|
||
|
||
this.addToHistory(expression, String(result));
|
||
}
|
||
},
|
||
|
||
performCalculation(op: string, first: number, second: number): number {
|
||
let result = 0;
|
||
switch(op) {
|
||
case '+': result = first + second; break;
|
||
case '-': result = first - second; break;
|
||
case '×': result = first * second; break;
|
||
case '÷': result = first / second; break;
|
||
default: return second;
|
||
}
|
||
const precision = 10000000000;
|
||
return Math.round(result * precision) / precision;
|
||
},
|
||
|
||
// 历史记录相关方法
|
||
addToHistory(expression: string, result: string) {
|
||
const list = this.data.historyList.slice();
|
||
list.unshift({ expression, result });
|
||
// 最多保留50条
|
||
if (list.length > 50) list.length = 50;
|
||
this.setData({ historyList: list });
|
||
wx.setStorageSync('CALC_HISTORY', list);
|
||
},
|
||
|
||
toggleHistory() {
|
||
this.vibrate();
|
||
this.setData({
|
||
showHistory: !this.data.showHistory,
|
||
showSettingsMenu: false
|
||
});
|
||
},
|
||
|
||
clearHistory() {
|
||
this.vibrate();
|
||
wx.removeStorageSync('CALC_HISTORY');
|
||
this.setData({ historyList: [] });
|
||
},
|
||
|
||
useHistoryResult(e: any) {
|
||
this.vibrate();
|
||
const result = e.currentTarget.dataset.result;
|
||
this.setData({
|
||
displayValue: result,
|
||
waitingForSecondOperand: false, // Treat as a fresh input that can be operated on
|
||
showHistory: false
|
||
});
|
||
},
|
||
|
||
copyResult() {
|
||
this.vibrate();
|
||
const { displayValue } = this.data;
|
||
wx.setClipboardData({
|
||
data: displayValue,
|
||
success: () => {
|
||
wx.showToast({
|
||
title: '复制成功',
|
||
icon: 'success'
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
console.error('复制失败:', err);
|
||
// 隐私协议未声明或用户拒绝会导致复制失败 (errno 112)
|
||
if (err.errno === 112 || (err.errMsg && err.errMsg.indexOf('privacy') > -1)) {
|
||
wx.showModal({
|
||
title: '提示',
|
||
content: '无法自动复制,请长按显示区域手动复制',
|
||
showCancel: false,
|
||
confirmText: '知道了'
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|