Files
vscode-workbench/docs/superpowers/plans/2026-07-28-scientific-calculator.md
T

16 KiB
Raw Blame History

科学计算器功能扩展实现计划

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"

测试要点

  1. 基础四则运算回归:确保原有功能不受影响
  2. 模式切换:基础 ↔ 科学模式切换正常
  3. 三角函数:sin(30°) = 0.5, cos(60°) = 0.5
  4. DEG/RAD 切换:同一角度在不同模式下结果不同
  5. 对数:log(100) = 2, ln(e) = 1
  6. 错误处理:负数开方、除以零、阶乘负数都显示 Error
  7. 记忆功能:MC/MR/M+/M− 正确工作
  8. 状态指示:M 标记和 DEG/RAD 指示器正确显示