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

705 lines
16 KiB
Markdown
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.
# 科学计算器功能扩展实现计划
> **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'`。
新增函数:
```typescript
const toggleMode = () => {
setState((prev) => ({
...prev,
mode: prev.mode === 'basic' ? 'scientific' : 'basic',
}));
};
```
- [ ] **Step 2: 在 display 区域上方渲染模式切换标签栏**
在 `calc-display` 之前添加:
```tsx
<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 中添加模式切换栏样式**
```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` 节中添加:
```json
"mode": {
"basic": "基础",
"scientific": "科学"
}
```
- [ ] **Step 5: 提交**
```bash
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 接口**
```typescript
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: 更新初始状态**
```typescript
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: 新增记忆操作函数**
```typescript
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: 新增角度模式切换函数**
```typescript
const toggleAngleMode = () => {
setState((prev) => ({
...prev,
angleMode: prev.angleMode === 'DEG' ? 'RAD' : 'DEG',
}));
};
```
- [ ] **Step 5: 新增反函数模式切换函数**
```typescript
const toggleInvMode = () => {
setState((prev) => ({ ...prev, invMode: !prev.invMode }));
};
```
- [ ] **Step 6: 提交**
```bash
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 数组前添加科学函数按钮定义**
```typescript
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**
```tsx
<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: 添加科学函数区样式**
```css
.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: 提交**
```bash
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 辅助函数**
```typescript
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 函数**
```typescript
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: 提交**
```bash
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³/√/³√**
```typescript
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(双目运算)**
```typescript
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**
```typescript
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: 提交**
```bash
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: 实现阶乘**
```typescript
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: 实现倒数**
```typescript
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 常数插入**
```typescript
const insertPi = () => {
setState((prev) => ({
...prev,
current: String(Math.PI),
overwrite: true,
}));
};
const insertE = () => {
setState((prev) => ({
...prev,
current: String(Math.E),
overwrite: true,
}));
};
```
- [ ] **Step 4: 提交**
```bash
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**
```typescript
const openParen = () => {
// 简化实现:在历史中记录括号
setState((prev) => ({
...prev,
history: prev.history + "(",
parenOpen: true,
}));
};
const closeParen = () => {
setState((prev) => ({
...prev,
history: prev.history + ")",
parenOpen: false,
}));
};
```
- [ ] **Step 2: 提交**
```bash
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 中添加错误信息**
```json
"calculator": {
"...": "...",
"errorTanUndefined": "tan 在此角度无定义",
"errorLogNegative": "对数的真数必须大于 0",
"errorFactorial": "阶乘仅支持非负整数",
"errorDivideByZero": "不能除以零",
"mode": {
"basic": "基础",
"scientific": "科学"
}
}
```
- [ ] **Step 2: 在 en.json 中添加错误信息**
```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: 提交**
```bash
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 指示器正确显示