回复
#星光不负 码向未来# 计算器开发 原创
鸿蒙小白001
发布于 2025-10-24 10:16
浏览
0收藏
设计思路
- 使用Grid布局实现计算器按钮
- 使用LocalStorage实现历史记录的本地保存
- 实现基本的四则运算和清除功能
- 提供历史记录查看和删除功能
数据库管理
import relationalStore from '@ohos.data.relationalStore';
export interface CalculationRecord {
id?: number;
expression: string;
result: string;
timestamp: number;
}
export class CalculatorDB {
private rdbStore: relationalStore.RdbStore | null = null;
private readonly TABLE_NAME: string = 'calculation_history';
private readonly STORE_CONFIG: relationalStore.StoreConfig = {
name: 'calculator.db',
securityLevel: relationalStore.SecurityLevel.S1
};
private readonly SQL_CREATE_TABLE: string = `
CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
expression TEXT NOT NULL,
result TEXT NOT NULL,
timestamp INTEGER NOT NULL
)
`;
async initialize(): Promise<void> {
try {
this.rdbStore = await relationalStore.getRdbStore(globalThis.context, this.STORE_CONFIG);
await this.rdbStore.executeSql(this.SQL_CREATE_TABLE);
console.info('CalculatorDB initialized successfully');
} catch (error) {
console.error(`Failed to initialize database: ${JSON.stringify(error)}`);
}
}
async addRecord(record: CalculationRecord): Promise<number> {
if (!this.rdbStore) {
console.error('Database not initialized');
return -1;
}
try {
const valueBucket: relationalStore.ValuesBucket = {
'expression': record.expression,
'result': record.result,
'timestamp': record.timestamp
};
const insertId = await this.rdbStore.insert(this.TABLE_NAME, valueBucket);
console.info('Record added successfully, id:', insertId);
return insertId;
} catch (error) {
console.error(`Failed to add record: ${JSON.stringify(error)}`);
return -1;
}
}
async getAllRecords(): Promise<CalculationRecord[]> {
if (!this.rdbStore) {
console.error('Database not initialized');
return [];
}
try {
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.orderByDesc('timestamp');
const resultSet = await this.rdbStore.query(predicates,
['id', 'expression', 'result', 'timestamp']);
const records: CalculationRecord[] = [];
while (resultSet.goToNextRow()) {
records.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
expression: resultSet.getString(resultSet.getColumnIndex('expression')),
result: resultSet.getString(resultSet.getColumnIndex('result')),
timestamp: resultSet.getLong(resultSet.getColumnIndex('timestamp'))
});
}
resultSet.close();
return records;
} catch (error) {
console.error(`Failed to query records: ${JSON.stringify(error)}`);
return [];
}
}
async clearAllRecords(): Promise<boolean> {
if (!this.rdbStore) {
console.error('Database not initialized');
return false;
}
try {
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
const deletedRows = await this.rdbStore.delete(predicates);
console.info('All records cleared, deleted rows:', deletedRows);
return deletedRows > 0;
} catch (error) {
console.error(`Failed to clear records: ${JSON.stringify(error)}`);
return false;
}
}
async deleteRecordById(id: number): Promise<boolean> {
if (!this.rdbStore) {
console.error('Database not initialized');
return false;
}
try {
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.equalTo('id', id);
const deletedRows = await this.rdbStore.delete(predicates);
return deletedRows > 0;
} catch (error) {
console.error(`Failed to delete record: ${JSON.stringify(error)}`);
return false;
}
}
}计算引擎
import { OperationState } from "../model/OperationState";
export class CalculatorEngine {
private currentInput: string = '0';
private previousInput: string = '';
private operator: string = '';
private shouldResetInput: boolean = false;
private lastCalculation: string = '';
// 处理数字输入
inputDigit(digit: string): void {
if (this.shouldResetInput) {
this.currentInput = '0';
this.shouldResetInput = false;
}
if (this.currentInput === '0' && digit !== '.') {
this.currentInput = digit;
} else if (digit === '.' && this.currentInput.includes('.')) {
return;
} else if (digit === '.' && this.currentInput === '0') {
this.currentInput = '0.';
} else if (this.currentInput.length < 15) { // 限制输入长度
this.currentInput += digit;
}
}
// 处理操作符输入
inputOperator(op: string): void {
if (this.currentInput === 'Error') {
return;
}
if (this.operator && !this.shouldResetInput && this.previousInput) {
this.calculate();
}
if (this.currentInput !== '' && this.currentInput !== 'Error') {
this.previousInput = this.currentInput;
this.operator = op;
this.shouldResetInput = true;
}
}
// 执行计算
calculate(): boolean {
if (!this.previousInput || !this.operator) {
return false;
}
const prev = parseFloat(this.previousInput);
const current = parseFloat(this.currentInput);
if (isNaN(prev) || isNaN(current)) {
this.currentInput = 'Error';
return false;
}
let result: number;
try {
switch (this.operator) {
case '+':
result = prev + current;
break;
case '-':
result = prev - current;
break;
case '×':
result = prev * current;
break;
case '÷':
if (current === 0) {
throw new Error('Division by zero');
}
result = prev / current;
break;
default:
return false;
}
// 处理浮点数精度问题
result = this.formatResult(result);
this.currentInput = result.toString();
this.lastCalculation = `${this.previousInput} ${this.operator} ${this.currentInput}`;
this.previousInput = '';
this.operator = '';
this.shouldResetInput = true;
return true;
} catch (error) {
this.currentInput = 'Error';
this.previousInput = '';
this.operator = '';
this.shouldResetInput = true;
return false;
}
}
// 格式化结果,处理精度
private formatResult(value: number): number {
if (!isFinite(value)) {
throw new Error('Invalid result');
}
// 处理浮点数精度问题
if (!Number.isInteger(value)) {
// 使用toFixed处理小数精度,然后转回数字
return parseFloat(value.toFixed(10).replace(/\.?0+$/, ''));
}
return value;
}
// 处理等号
handleEquals(): boolean {
if (this.operator && this.previousInput) {
return this.calculate();
}
return false;
}
// 清除所有
clear(): void {
this.currentInput = '0';
this.previousInput = '';
this.operator = '';
this.shouldResetInput = false;
this.lastCalculation = '';
}
// 清除当前输入
clearEntry(): void {
this.currentInput = '0';
this.shouldResetInput = false;
}
// 删除最后一位
deleteLast(): void {
if (this.shouldResetInput || this.currentInput === 'Error') {
this.currentInput = '0';
this.shouldResetInput = false;
return;
}
if (this.currentInput.length > 1) {
this.currentInput = this.currentInput.slice(0, -1);
// 如果删除后只剩下负号,重置为0
if (this.currentInput === '-') {
this.currentInput = '0';
}
} else {
this.currentInput = '0';
}
}
// 获取当前显示内容
getCurrentDisplay(): string {
return this.currentInput;
}
// 获取当前表达式
getCurrentExpression(): string {
if (this.previousInput && this.operator) {
if (this.shouldResetInput) {
return `${this.previousInput} ${this.operator}`;
} else {
return `${this.previousInput} ${this.operator} ${this.currentInput}`;
}
}
return '';
}
// 获取最后一次计算的完整表达式(用于历史记录)
getLastCalculation(): string {
return this.lastCalculation;
}
// 百分比计算
calculatePercentage(): void {
if (this.currentInput !== 'Error' && this.currentInput !== '0') {
const value = parseFloat(this.currentInput);
if (!isNaN(value)) {
const result = value / 100;
this.currentInput = this.formatResult(result).toString();
this.shouldResetInput = true;
}
}
}
// 正负号切换
toggleSign(): void {
if (this.currentInput !== 'Error' && this.currentInput !== '0') {
if (this.currentInput.startsWith('-')) {
this.currentInput = this.currentInput.slice(1);
} else {
this.currentInput = '-' + this.currentInput;
}
}
}
// 获取计算器状态(用于调试)
getState() {
let operationState = new OperationState()
operationState.currentInput = this.currentInput
operationState.previousInput = this.previousInput
operationState.operator = this.operator
operationState.shouldResetInput = this.shouldResetInput
return operationState;
}
}export class OperationState{
currentInput: string = ""
previousInput: string = ""
operator: string = ""
shouldResetInput: boolean = false;
}计算器页面


import { CalculationRecord, CalculatorDB } from '../utils/CalculatorDB';
import { CalculatorEngine } from '../utils/CalculatorEngine';
@Entry
@Component
struct Index {
@State currentDisplay: string = '0';
@State expression: string = '';
@State historyRecords: CalculationRecord[] = [];
@State showHistory: boolean = false;
@State isError: boolean = false;
private calculator: CalculatorEngine = new CalculatorEngine();
private calculatorDB:CalculatorDB = new CalculatorDB()
aboutToAppear() {
this.calculatorDB.initialize().then(()=>{
this.loadHistory();
})
}
async loadHistory() {
try {
this.historyRecords = await this.calculatorDB.getAllRecords();
} catch (error) {
console.error('Failed to load history:', error);
}
}
build() {
Column() {
if (this.showHistory) {
this.HistoryView()
} else {
this.CalculatorView()
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
@Builder CalculatorView() {
Column({ space: 20 }) {
// 显示区域
Column({ space: 10 }) {
Text(this.expression)
.fontSize(18)
.fontColor('#666')
.textAlign(TextAlign.End)
.width('90%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.currentDisplay)
.fontSize(48)
.fontColor(this.isError ? '#FF4444' : '#333333')
.textAlign(TextAlign.End)
.width('90%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.width('90%')
.shadow({ radius: 8, color: '#1A000000', offsetX: 0, offsetY: 2 })
// 按钮区域
Grid() {
// 第一行
GridItem() {
Button('C')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FF6B6B')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleClear())
}
GridItem() {
Button('±')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#E0E0E0')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleToggleSign())
}
GridItem() {
Button('%')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#E0E0E0')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handlePercentage())
}
GridItem() {
Button('÷')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#4CD964')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleOperator('÷'))
}
// 第二行
GridItem() {
Button('7')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('7'))
}
GridItem() {
Button('8')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('8'))
}
GridItem() {
Button('9')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('9'))
}
GridItem() {
Button('×')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#4CD964')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleOperator('×'))
}
// 第三行
GridItem() {
Button('4')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('4'))
}
GridItem() {
Button('5')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('5'))
}
GridItem() {
Button('6')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('6'))
}
GridItem() {
Button('-')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#4CD964')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleOperator('-'))
}
// 第四行
GridItem() {
Button('1')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('1'))
}
GridItem() {
Button('2')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('2'))
}
GridItem() {
Button('3')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('3'))
}
GridItem() {
Button('+')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#4CD964')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleOperator('+'))
}
// 第五行
GridItem() {
Button('0')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('0'))
}
GridItem() {
Button('.')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FFFFFF')
.fontColor('#333333')
.borderRadius(12)
.onClick(() => this.handleDigit('.'))
}
GridItem() {
Button('=')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.backgroundColor('#007AFF')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.handleEquals())
}
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.width('90%')
.height(400)
// 历史记录按钮
Button('查看历史记录')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.backgroundColor('#9370DB')
.fontColor('#FFFFFF')
.width('90%')
.height(50)
.borderRadius(12)
.onClick(() => {
this.showHistory = true;
this.loadHistory();
})
}
.justifyContent(FlexAlign.Center)
.width('100%')
.height('100%')
.padding({ top: 40, bottom: 20 })
}
@Builder HistoryView() {
Column() {
// 标题栏
Row() {
Button('← 返回')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.backgroundColor('#9370DB')
.fontColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.onClick(() => this.showHistory = false)
Text('计算历史')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.layoutWeight(1)
.textAlign(TextAlign.Center)
Button('清空')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.backgroundColor('#FF6B6B')
.fontColor('#FFFFFF')
.borderRadius(8)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.onClick(async () => {
const success = await this.calculatorDB.clearAllRecords();
if (success) {
this.historyRecords = [];
}
})
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.alignItems(VerticalAlign.Center)
if (this.historyRecords.length === 0) {
Column() {
Image($r('app.media.startIcon'))
.width(120)
.height(120)
.margin({ bottom: 20 })
Text('暂无历史记录')
.fontSize(18)
.fontColor('#999999')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
} else {
List({ space: 12 }) {
ForEach(this.historyRecords, (record: CalculationRecord, index?: number) => {
ListItem() {
Row() {
Column({ space: 6 }) {
Text(record.expression)
.fontSize(16)
.fontColor('#666666')
.textAlign(TextAlign.Start)
.width('100%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(`= ${record.result}`)
.fontSize(20)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Start)
.width('100%')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.formatTimestamp(record.timestamp))
.fontSize(12)
.fontColor('#999999')
.textAlign(TextAlign.Start)
.width('100%')
}
.layoutWeight(1)
Button('删除')
.fontSize(12)
.backgroundColor('#FF6B6B')
.fontColor('#FFFFFF')
.borderRadius(6)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.onClick(async () => {
if (record.id) {
const success = await this.calculatorDB.deleteRecordById(record.id);
if (success) {
this.historyRecords.splice(index!, 1);
this.historyRecords = [...this.historyRecords];
}
}
})
}
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.width('100%')
}
}, (record: CalculationRecord) => record.id?.toString() ?? record.timestamp.toString())
}
.width('100%')
.layoutWeight(1)
.padding(16)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
private handleDigit(digit: string): void {
if (this.isError) {
this.calculator.clear();
this.isError = false;
}
this.calculator.inputDigit(digit);
this.updateDisplay();
}
private handleOperator(operator: string): void {
if (this.isError) {
this.calculator.clear();
this.isError = false;
}
this.calculator.inputOperator(operator);
this.updateDisplay();
}
private async handleEquals(): Promise<void> {
if (this.isError) {
this.calculator.clear();
this.isError = false;
this.updateDisplay();
return;
}
const success = this.calculator.handleEquals();
if (success) {
const expression = this.calculator.getLastCalculation();
const result = this.calculator.getCurrentDisplay();
if (result !== 'Error') {
const record: CalculationRecord = {
expression: expression,
result: result,
timestamp: new Date().getTime()
};
await this.calculatorDB.addRecord(record);
await this.loadHistory();
} else {
this.isError = true;
}
}
this.updateDisplay();
}
private handleClear(): void {
this.calculator.clear();
this.isError = false;
this.updateDisplay();
}
private handleToggleSign(): void {
if (!this.isError) {
this.calculator.toggleSign();
this.updateDisplay();
}
}
private handlePercentage(): void {
if (!this.isError) {
this.calculator.calculatePercentage();
this.updateDisplay();
}
}
private updateDisplay(): void {
this.currentDisplay = this.calculator.getCurrentDisplay();
this.expression = this.calculator.getCurrentExpression();
if (this.currentDisplay === 'Error') {
this.isError = true;
}
}
private formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const recordDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
if (recordDate.getTime() === today.getTime()) {
return `今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
} else {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
}
}功能特点
- 完整的计算功能:
- 基本四则运算(+、-、×、÷)
- 正负号切换
- 百分比计算
- 小数点支持
- 历史记录功能:
- 自动保存计算历史
- 历史记录本地存储
- 清除历史记录功能
- 限制历史记录数量(最多50条)
- 用户界面:
- 响应式布局设计
- 清晰的视觉层次
- 符合HarmonyOS设计规范
- 支持横竖屏适配
- 数据持久化:
- 使用LocalStorage实现本地存储
- 应用重启后历史记录不丢失
使用说明
- 数字输入:点击数字按钮输入数字
- 运算操作:点击运算符按钮选择运算类型
- 等号计算:点击"="执行计算并保存到历史记录
- 清除功能:点击"C"清除当前计算
- 历史查看:在底部查看历史计算记录
- 清除历史:点击"清除历史"按钮清空所有历史记录
这个计算器应用完全使用ArkTS开发,符合HarmonyOS Next的开发规范,具备良好的用户体验和完整的功能实现。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
赞
收藏
回复
相关推荐



















