Files
InfoGenie/InfoGenie-frontend/public/toolbox/计算器/app.js
2025-12-13 20:53:50 +08:00

225 lines
7.0 KiB
JavaScript
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.
(() => {
const { createApp, ref, computed, watch } = Vue;
// 检测是否可用 Math.js
const hasMath = typeof math !== 'undefined';
if (hasMath) {
math.config({ number: 'BigNumber', precision: 64 });
}
// 保存原始三角函数以便覆盖时调用
const originalSin = hasMath ? math.sin : null;
const originalCos = hasMath ? math.cos : null;
const originalTan = hasMath ? math.tan : null;
// 角度转换因子deg -> rad
const RAD_FACTOR = hasMath ? math.divide(math.pi, math.bignumber(180)) : (Math.PI / 180);
// 动态角度模式变量供三角函数使用
let angleModeVar = 'deg';
function sinWrapper(x) {
try {
if (angleModeVar === 'deg') {
const xr = hasMath ? math.multiply(x, RAD_FACTOR) : (Number(x) * RAD_FACTOR);
return hasMath ? originalSin(xr) : Math.sin(xr);
}
return hasMath ? originalSin(x) : Math.sin(Number(x));
} catch (e) { throw e; }
}
function cosWrapper(x) {
try {
if (angleModeVar === 'deg') {
const xr = hasMath ? math.multiply(x, RAD_FACTOR) : (Number(x) * RAD_FACTOR);
return hasMath ? originalCos(xr) : Math.cos(xr);
}
return hasMath ? originalCos(x) : Math.cos(Number(x));
} catch (e) { throw e; }
}
function tanWrapper(x) {
try {
if (angleModeVar === 'deg') {
const xr = hasMath ? math.multiply(x, RAD_FACTOR) : (Number(x) * RAD_FACTOR);
return hasMath ? originalTan(xr) : Math.tan(xr);
}
return hasMath ? originalTan(x) : Math.tan(Number(x));
} catch (e) { throw e; }
}
// 覆盖三角函数以支持角度模式Math.js 可用时)
if (hasMath) {
math.import({ sin: sinWrapper, cos: cosWrapper, tan: tanWrapper }, { override: true });
}
function formatBig(value) {
try {
if (value == null) return '';
if (hasMath) {
return math.format(value, {
notation: 'auto',
precision: 14,
lowerExp: -6,
upperExp: 15,
});
} else {
const num = typeof value === 'number' ? value : Number(value);
if (!isFinite(num)) return '错误';
const str = num.toFixed(12);
return str.replace(/\.0+$/, '').replace(/(\.[0-9]*?)0+$/, '$1');
}
} catch (e) {
return String(value);
}
}
function normalize(exp) {
// 将显示符号标准化为计算符号,保留原字符不做删除
return exp
.replace(/×/g, '*')
.replace(/÷/g, '/')
.replace(/√/g, 'sqrt');
}
createApp({
setup() {
const expression = ref('');
const result = ref(hasMath ? math.bignumber(0) : 0);
const errorMsg = ref('');
const lastAns = ref(hasMath ? math.bignumber(0) : 0);
const angleMode = ref('deg');
watch(angleMode, (val) => { angleModeVar = val; });
const formattedExpression = computed(() => expression.value || '0');
const formattedResult = computed(() => errorMsg.value ? '' : formatBig(result.value));
function isParenthesesBalanced(s) {
let count = 0;
for (const ch of s) {
if (ch === '(') count++;
else if (ch === ')') count--;
if (count < 0) return false;
}
return count === 0;
}
function safeEvaluate(exp) {
errorMsg.value = '';
try {
const s = normalize(exp);
if (!s) { result.value = hasMath ? math.bignumber(0) : 0; return result.value; }
// 检测非法字符仅允许数字、运算符、括号、字母用于函数和ANS以及空白
if (/[^0-9\.\+\-\*\/\^\(\)a-zA-Z\s]/.test(s)) { throw new Error('错误'); }
if (!isParenthesesBalanced(s)) throw new Error('错误');
if (hasMath) {
const scope = { ANS: lastAns.value };
const res = math.evaluate(s, scope);
// 防止除以零等无效情况
if (res && res.isFinite && !res.isFinite()) { throw new Error('错误'); }
result.value = res;
return res;
} else {
// 原生回退:将表达式映射到安全本地函数
let expr = s
.replace(/\^/g, '**')
.replace(/sin\(/g, '__sin(')
.replace(/cos\(/g, '__cos(')
.replace(/tan\(/g, '__tan(')
.replace(/sqrt\(/g, '__sqrt(')
.replace(/\bANS\b/g, String(lastAns.value));
// 严格校验(只允许安全字符)
if (/[^0-9+\-*/()._^a-zA-Z\s]/.test(expr)) throw new Error('错误');
// 定义本地安全函数
const __sqrt = (x) => Math.sqrt(Number(x));
const __sin = (x) => angleModeVar === 'deg' ? Math.sin(Number(x) * Math.PI / 180) : Math.sin(Number(x));
const __cos = (x) => angleModeVar === 'deg' ? Math.cos(Number(x) * Math.PI / 180) : Math.cos(Number(x));
const __tan = (x) => angleModeVar === 'deg' ? Math.tan(Number(x) * Math.PI / 180) : Math.tan(Number(x));
const res = Function('__sqrt','__sin','__cos','__tan', `"use strict"; return (${expr});`)(__sqrt,__sin,__cos,__tan);
if (!isFinite(res)) throw new Error('错误');
result.value = res;
return res;
}
} catch (err) {
errorMsg.value = '错误';
return null;
}
}
watch(expression, (exp) => { safeEvaluate(exp); });
function press(token) {
// 避免连续两个小数点
if (token === '.' && expression.value.slice(-1) === '.') return;
expression.value += token;
}
function op(opSymbol) {
const last = expression.value.slice(-1);
if (/[\+\-×÷\*\/\^]/.test(last)) {
expression.value = expression.value.slice(0, -1) + opSymbol;
} else {
expression.value += opSymbol;
}
}
function func(fn) {
const map = { sqrt: 'sqrt', sin: 'sin', cos: 'cos', tan: 'tan' };
const f = map[fn] || fn;
expression.value += f + '(';
}
function square() {
expression.value += '^2';
}
function backspace() {
if (!expression.value) return;
expression.value = expression.value.slice(0, -1);
}
function clear() {
expression.value = '';
result.value = math.bignumber(0);
errorMsg.value = '';
}
function equals() {
const res = safeEvaluate(expression.value);
if (res != null) {
lastAns.value = res;
expression.value = formatBig(res);
result.value = res;
}
}
function ans() {
expression.value += 'ANS';
}
function setAngle(mode) {
angleMode.value = mode;
}
// 初始计算
safeEvaluate(expression.value);
return {
expression,
result,
errorMsg,
formattedExpression,
formattedResult,
angleMode,
setAngle,
press,
op,
func,
clear,
backspace,
equals,
square,
ans,
};
},
}).mount('#app');
})();