16 KiB
科学计算器功能扩展实现计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 将桌面端计算器从基础四则运算扩展为完整科学计算器,支持三角/反三角/对数/幂/根/阶乘/记忆/括号等功能
Architecture: 在 CalculatorDesktop 中新增模式切换标签栏(基础/科学),科学模式下显示函数区。状态管理扩展 memory/angleMode/invMode/parenOpen/mode。保持 4 列布局,宽度不变。
Tech Stack: React, TypeScript, CSS, Ant Design (message)
文件结构
- 修改:
src/pages/Calculator/Calculator.tsx— 扩展 CalculatorDesktop 组件 - 修改:
src/pages/Calculator/Calculator.css— 新增科学函数按键样式、模式切换栏 - 不修改:
src/pages/Calculator/CalculatorMobile.tsx— 移动端保持不变
Task 1: 新增模式切换标签栏
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Modify:
src/pages/Calculator/Calculator.css -
Step 1: 在 Calculator.tsx 中新增 mode 状态和切换逻辑
在 CalculatorState 中添加 mode: 'basic' | 'scientific',初始值为 'basic'。
新增函数:
const toggleMode = () => {
setState((prev) => ({
...prev,
mode: prev.mode === 'basic' ? 'scientific' : 'basic',
}));
};
- Step 2: 在 display 区域上方渲染模式切换标签栏
在 calc-display 之前添加:
<div className="calc-mode-bar">
<div className="calc-mode-tabs">
<button
className={`calc-mode-tab ${state.mode === 'basic' ? 'active' : ''}`}
onClick={toggleMode}
>
{t("calculator.mode.basic")}
</button>
<button
className={`calc-mode-tab ${state.mode === 'scientific' ? 'active' : ''}`}
onClick={toggleMode}
>
{t("calculator.mode.scientific")}
</button>
</div>
<div className="calc-mode-indicator">
{state.mode === 'scientific' && (
<span className="calc-angle-badge">{state.angleMode}</span>
)}
{state.memory !== 0 && <span className="calc-memory-badge">M</span>}
</div>
</div>
- Step 3: 在 Calculator.css 中添加模式切换栏样式
.calc-mode-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: #151928;
border-bottom: 1px solid rgba(51, 65, 85, 0.5);
}
.calc-mode-tabs {
display: flex;
gap: 4px;
}
.calc-mode-tab {
padding: 6px 16px;
font-size: 12px;
font-weight: 600;
border: none;
background: transparent;
color: #64748b;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.calc-mode-tab.active {
background: #6366f1;
color: #fff;
}
.calc-mode-tab:hover:not(.active) {
color: #94a3b8;
background: rgba(99, 102, 241, 0.1);
}
.calc-mode-indicator {
display: flex;
gap: 6px;
}
.calc-angle-badge,
.calc-memory-badge {
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 4px;
background: rgba(99, 102, 241, 0.2);
color: #a5b4fc;
}
.calc-memory-badge {
background: rgba(34, 197, 94, 0.2);
color: #86efac;
}
- Step 4: 添加国际化文本
在 zh.json 和 en.json 的 calculator 节中添加:
"mode": {
"basic": "基础",
"scientific": "科学"
}
- Step 5: 提交
git add src/pages/Calculator/Calculator.tsx src/pages/Calculator/Calculator.css src/locales/zh.json src/locales/en.json
git commit -m "feat(calculator): add mode toggle bar (basic/scientific)"
Task 2: 扩展状态管理
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Step 1: 扩展 CalculatorState 接口
interface CalculatorState {
current: string;
previous: string;
operator: Operator | null;
overwrite: boolean;
history: string;
memory: number; // 新增:记忆存储值
angleMode: 'DEG' | 'RAD'; // 新增:角度/弧度模式
invMode: boolean; // 新增:反函数模式(2nd键)
parenOpen: boolean; // 新增:是否有未闭合的括号
mode: 'basic' | 'scientific'; // 新增:当前模式
}
type Operator = '+' | '-' | '*' | '/' | '^' | 'root';
- Step 2: 更新初始状态
const [state, setState] = useState<CalculatorState>({
current: "0",
previous: "",
operator: null,
overwrite: false,
history: "",
memory: 0,
angleMode: 'DEG',
invMode: false,
parenOpen: false,
mode: 'basic',
});
- Step 3: 新增记忆操作函数
const memoryClear = () => {
setState((prev) => ({ ...prev, memory: 0 }));
};
const memoryRecall = () => {
setState((prev) => ({
...prev,
current: String(prev.memory),
overwrite: true,
}));
};
const memoryAdd = () => {
setState((prev) => ({
...prev,
memory: prev.memory + parseFloat(prev.current),
}));
};
const memorySubtract = () => {
setState((prev) => ({
...prev,
memory: prev.memory - parseFloat(prev.current),
}));
};
- Step 4: 新增角度模式切换函数
const toggleAngleMode = () => {
setState((prev) => ({
...prev,
angleMode: prev.angleMode === 'DEG' ? 'RAD' : 'DEG',
}));
};
- Step 5: 新增反函数模式切换函数
const toggleInvMode = () => {
setState((prev) => ({ ...prev, invMode: !prev.invMode }));
};
- Step 6: 提交
git add src/pages/Calculator/Calculator.tsx
git commit -m "feat(calculator): extend state management with memory, angle mode, inv mode"
Task 3: 新增科学函数区(科学模式专用)
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Modify:
src/pages/Calculator/Calculator.css -
Step 1: 在 buttons 数组前添加科学函数按钮定义
const sciButtons = [
{ label: state.invMode ? "sin⁻¹" : "sin", type: "function", onClick: sin },
{ label: state.invMode ? "cos⁻¹" : "cos", type: "function", onClick: cos },
{ label: state.invMode ? "tan⁻¹" : "tan", type: "function", onClick: tan },
{ label: state.invMode ? "x³" : "x²", type: "function", onClick: state.invMode ? cube : square },
{ label: "log", type: "function", onClick: log10 },
{ label: state.invMode ? "eˣ" : "ln", type: "function", onClick: state.invMode ? exp : ln },
{ label: state.invMode ? "³√" : "√", type: "function", onClick: state.invMode ? cbrt : sqrt },
{ label: "π", type: "function", onClick: insertPi },
{ label: "e", type: "function", onClick: insertE },
{ label: state.invMode ? "x√y" : "xʸ", type: "operator", onClick: state.invMode ? nthRoot : power },
{ label: "n!", type: "function", onClick: factorial },
{ label: "1/x", type: "function", onClick: reciprocal },
{ label: "DEG/RAD", type: "function", onClick: toggleAngleMode },
{ label: "2nd", type: "function", onClick: toggleInvMode },
{ label: "MC", type: "memory", onClick: memoryClear },
{ label: "MR", type: "memory", onClick: memoryRecall },
{ label: "M+", type: "memory", onClick: memoryAdd },
{ label: "M−", type: "memory", onClick: memorySubtract },
{ label: "(", type: "function", onClick: openParen },
{ label: ")", type: "function", onClick: closeParen },
];
- Step 2: 在渲染 keypad 时,科学模式下显示 sciButtons
<div className="calc-keypad">
{state.mode === 'scientific' && (
<div className="calc-sci-row">
{sciButtons.slice(0, 10).map((btn, idx) => (
<button
key={`sci-${idx}`}
className={`calc-btn calc-btn-${btn.type} ${btn.className || ""}`}
onClick={btn.onClick}
>
{btn.label}
</button>
))}
</div>
)}
{/* 原有的基础按钮 */}
{buttons.map((btn, idx) => (
<button
key={idx}
className={`calc-btn calc-btn-${btn.type} ${btn.className || ""}`}
onClick={btn.onClick}
>
{btn.label}
</button>
))}
</div>
- Step 3: 添加科学函数区样式
.calc-sci-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
padding: 8px;
background: rgba(99, 102, 241, 0.05);
border-bottom: 1px solid rgba(51, 65, 85, 0.3);
}
.calc-sci-row .calc-btn {
font-size: 11px;
padding: 8px 4px;
}
[data-theme="dark"] .calc-sci-row {
background: rgba(99, 102, 241, 0.08);
border-bottom-color: rgba(51, 65, 85, 0.5);
}
- Step 4: 提交
git add src/pages/Calculator/Calculator.tsx src/pages/Calculator/Calculator.css
git commit -m "feat(calculator): add scientific function row"
Task 4: 实现三角函数
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Step 1: 实现 toRad/fromRad 辅助函数
const toRad = (val: number) =>
state.angleMode === 'DEG' ? val * Math.PI / 180 : val;
const fromRad = (val: number) =>
state.angleMode === 'DEG' ? val * 180 / Math.PI : val;
- Step 2: 实现 sin/cos/tan 函数
const sin = () => {
const num = parseFloat(state.current);
const result = Math.sin(toRad(num));
setState((prev) => ({
...prev,
current: String(result),
overwrite: true,
}));
};
const cos = () => {
const num = parseFloat(state.current);
const result = Math.cos(toRad(num));
setState((prev) => ({
...prev,
current: String(result),
overwrite: true,
}));
};
const tan = () => {
const num = parseFloat(state.current);
const rad = toRad(num);
// 检查 tan 的无定义点 (90°, 270°, etc.)
const cosVal = Math.cos(rad);
if (Math.abs(cosVal) < 1e-10) {
message.error(t("calculator.errorTanUndefined"));
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
const result = Math.tan(rad);
setState((prev) => ({
...prev,
current: String(result),
overwrite: true,
}));
};
- Step 3: 提交
git add src/pages/Calculator/Calculator.tsx
git commit -m "feat(calculator): implement trigonometric functions (sin/cos/tan)"
Task 5: 实现对数/指数/幂函数
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Step 1: 实现 log/ln/x²/x³/√/³√
const log10 = () => {
const num = parseFloat(state.current);
if (num <= 0) {
message.error(t("calculator.errorLogNegative"));
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
setState((prev) => ({
...prev,
current: String(Math.log10(num)),
overwrite: true,
}));
};
const ln = () => {
const num = parseFloat(state.current);
if (num <= 0) {
message.error(t("calculator.errorLogNegative"));
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
setState((prev) => ({
...prev,
current: String(Math.log(num)),
overwrite: true,
}));
};
const square = () => {
const num = parseFloat(state.current);
setState((prev) => ({
...prev,
current: String(num * num),
overwrite: true,
}));
};
const cube = () => {
const num = parseFloat(state.current);
setState((prev) => ({
...prev,
current: String(num * num * num),
overwrite: true,
}));
};
const cbrt = () => {
const num = parseFloat(state.current);
setState((prev) => ({
...prev,
current: String(Math.cbrt(num)),
overwrite: true,
}));
};
const exp = () => {
const num = parseFloat(state.current);
setState((prev) => ({
...prev,
current: String(Math.exp(num)),
overwrite: true,
}));
};
- Step 2: 实现 xʸ 和 x√y(双目运算)
const power = () => {
setState((prev) => ({
...prev,
previous: prev.current,
operator: '^',
overwrite: true,
history: `${prev.current} ^`,
}));
};
const nthRoot = () => {
setState((prev) => ({
...prev,
previous: prev.current,
operator: 'root',
overwrite: true,
history: `${prev.current} √`,
}));
};
- Step 3: 更新 compute 函数支持 ^ 和 root
const compute = (a: string, b: string, op: Operator): string => {
const numA = parseFloat(a);
const numB = parseFloat(b);
if (isNaN(numA) || isNaN(numB)) return "0";
let result = 0;
switch (op) {
case "+": result = numA + numB; break;
case "-": result = numA - numB; break;
case "*": result = numA * numB; break;
case "/": result = numB === 0 ? NaN : numA / numB; break;
case "^": result = Math.pow(numA, numB); break;
case "root": result = Math.pow(numA, 1 / numB); break;
default: return b;
}
if (isNaN(result) || !isFinite(result)) return "Error";
const str = result.toString();
return str.length > 15 ? result.toExponential(8) : str;
};
- Step 4: 提交
git add src/pages/Calculator/Calculator.tsx
git commit -m "feat(calculator): implement logarithm, exponent, and power functions"
Task 6: 实现阶乘/倒数/常数
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Step 1: 实现阶乘
const factorial = () => {
const num = parseFloat(state.current);
if (num < 0 || !Number.isInteger(num)) {
message.error(t("calculator.errorFactorial"));
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
if (num > 170) {
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
let result = 1;
for (let i = 2; i <= num; i++) result *= i;
setState((prev) => ({
...prev,
current: String(result),
overwrite: true,
}));
};
- Step 2: 实现倒数
const reciprocal = () => {
const num = parseFloat(state.current);
if (num === 0) {
message.error(t("calculator.errorDivideByZero"));
setState((prev) => ({ ...prev, current: "Error" }));
return;
}
setState((prev) => ({
...prev,
current: String(1 / num),
overwrite: true,
}));
};
- Step 3: 实现 π 和 e 常数插入
const insertPi = () => {
setState((prev) => ({
...prev,
current: String(Math.PI),
overwrite: true,
}));
};
const insertE = () => {
setState((prev) => ({
...prev,
current: String(Math.E),
overwrite: true,
}));
};
- Step 4: 提交
git add src/pages/Calculator/Calculator.tsx
git commit -m "feat(calculator): implement factorial, reciprocal, and constants"
Task 7: 实现括号支持
Files:
-
Modify:
src/pages/Calculator/Calculator.tsx -
Step 1: 实现 openParen/closeParen
const openParen = () => {
// 简化实现:在历史中记录括号
setState((prev) => ({
...prev,
history: prev.history + "(",
parenOpen: true,
}));
};
const closeParen = () => {
setState((prev) => ({
...prev,
history: prev.history + ")",
parenOpen: false,
}));
};
- Step 2: 提交
git add src/pages/Calculator/Calculator.tsx
git commit -m "feat(calculator): add parenthesis support"
Task 8: 国际化文本补充
Files:
-
Modify:
src/locales/zh.json -
Modify:
src/locales/en.json -
Step 1: 在 zh.json 中添加错误信息
"calculator": {
"...": "...",
"errorTanUndefined": "tan 在此角度无定义",
"errorLogNegative": "对数的真数必须大于 0",
"errorFactorial": "阶乘仅支持非负整数",
"errorDivideByZero": "不能除以零",
"mode": {
"basic": "基础",
"scientific": "科学"
}
}
- Step 2: 在 en.json 中添加错误信息
"calculator": {
"...": "...",
"errorTanUndefined": "tan is undefined at this angle",
"errorLogNegative": "Logarithm requires positive number",
"errorFactorial": "Factorial only supports non-negative integers",
"errorDivideByZero": "Cannot divide by zero",
"mode": {
"basic": "Basic",
"scientific": "Scientific"
}
}
- Step 3: 提交
git add src/locales/zh.json src/locales/en.json
git commit -m "feat(calculator): add i18n error messages and mode labels"
测试要点
- 基础四则运算回归:确保原有功能不受影响
- 模式切换:基础 ↔ 科学模式切换正常
- 三角函数:sin(30°) = 0.5, cos(60°) = 0.5
- DEG/RAD 切换:同一角度在不同模式下结果不同
- 对数:log(100) = 2, ln(e) = 1
- 错误处理:负数开方、除以零、阶乘负数都显示 Error
- 记忆功能:MC/MR/M+/M− 正确工作
- 状态指示:M 标记和 DEG/RAD 指示器正确显示