欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

atomgit仓库地址: https://atomgit.com/m0_66062719/youhaojisuanqi

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 项目概述

1.1 背景介绍

随着汽车保有量的增加,车辆油耗管理成为车主日常关注的重要事项。一个专业的油耗计算器可以帮助用户记录加油信息、自动计算油耗和费用,并提供统计分析和趋势图表,帮助用户更好地了解和管理车辆燃油消耗情况。

本文详细介绍如何在鸿蒙PC平台上,基于Electron框架实现一个功能完善的油耗计算器应用。

1.2 技术选型

维度 技术 版本 选型理由
框架 Electron 18.x 跨平台桌面应用框架,支持Web技术栈
渲染引擎 Chromium 95+ 高性能HTML/CSS渲染
语言 JavaScript ES6+ 异步编程友好,适合数据处理
UI技术 HTML5 + CSS3 - 现代化界面设计和动画效果
存储 localStorage - 轻量级本地数据持久化

1.3 功能特性

本项目实现了一个功能丰富的油耗计算器,主要特性包括:

  • 加油记录管理:添加、编辑、删除加油记录
  • 实时油耗计算:自动计算油耗、费用等指标
  • 统计分析:总加油次数、累计加油量、累计行驶、平均油耗
  • 趋势图表:柱状图展示最近12条记录的油耗趋势
  • 数据持久化:使用localStorage本地存储

2. 架构设计

2.1 整体架构

┌─────────────────────────────────────────────────────┐
│                      Electron主进程                   │
├─────────────────────────────────────────────────────┤
│  main.js                                            │
│  ├── 创建BrowserWindow                              │
│  ├── 加载HTML页面                                   │
│  └── 菜单与快捷键管理                                │
└─────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────┐
│                      渲染进程                        │
├─────────────────────────────────────────────────────┤
│  fuel_calculator.html                              │
│  ├── 表单输入层(日期、加油量、里程、油价)           │
│  ├── 计算结果层(油耗、费用等)                      │
│  ├── 统计概览层(统计卡片)                         │
│  ├── 趋势图表层(柱状图)                           │
│  └── 历史记录层(表格展示)                         │
├─────────────────────────────────────────────────────┤
│  fuel_calculator.js                                │
│  ├── FuelCalculator类(核心控制器)                 │
│  ├── 计算模块(油耗、费用计算)                     │
│  ├── 统计模块(累计统计、平均值)                    │
│  └── 图表模块(柱状图生成)                         │
└─────────────────────────────────────────────────────┘

2.2 核心组件职责

组件 职责 关键方法
FuelCalculator 主控制器,协调整体逻辑 constructor, addRecord, updateUI
计算模块 油耗和费用计算 calculateCurrent
统计模块 累计统计和平均值计算 updateStats
图表模块 柱状图生成和渲染 updateChart

2.3 数据模型

// 加油记录数据结构
{
    id: 时间戳,                    // 唯一标识
    date: "2024-01-15",           // 加油日期
    amount: "45.5",               // 加油量(升)
    distance: "520.0",            // 行驶里程(公里)
    price: "7.89",                // 油价(元/升)
    consumption: "8.75",          // 油耗(L/100km)
    totalCost: "358.99",          // 加油费用(元)
    costPerKm: "0.69",            // 每公里费用(元)
    costPer100Km: "69.00"         // 每百公里费用(元)
}

2.4 计算公式

指标 公式 说明
油耗 (加油量 / 行驶里程) × 100 L/100km
加油费用 加油量 × 油价
每公里费用 加油费用 / 行驶里程 元/km
每百公里费用 油耗 × 油价 元/100km
平均油耗 (累计加油量 / 累计里程) × 100 L/100km

3. 核心代码实现

3.1 主入口HTML结构

HTML页面采用分层设计,包含表单输入区、结果展示区、统计概览区、趋势图区和历史记录区。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>油耗计算器</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
        }
        
        .card {
            background: rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
            border-radius: 16px;
            padding: 25px;
            margin-bottom: 20px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        
        .form-row {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
        }
        
        .form-control {
            width: 100%;
            padding: 12px 16px;
            background: rgba(0, 0, 0, 0.3);
            border: 1px solid rgba(255, 255, 255, 0.2);
            border-radius: 8px;
            color: #fff;
            font-size: 1rem;
            outline: none;
        }
        
        .form-control:focus {
            border-color: #4fd1c5;
            box-shadow: 0 0 0 3px rgba(79, 209, 197, 0.2);
        }
        
        .btn-primary {
            background: linear-gradient(135deg, #4fd1c5, #38b2ac);
            color: #1a202c;
            padding: 14px 30px;
            border: none;
            border-radius: 10px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 30px rgba(79, 209, 197, 0.3);
        }
        
        .results {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
        }
        
        .result-card {
            background: rgba(79, 209, 197, 0.1);
            border: 1px solid rgba(79, 209, 197, 0.3);
            border-radius: 12px;
            padding: 20px;
            text-align: center;
        }
        
        .result-value {
            font-size: 2rem;
            font-weight: 700;
            color: #4fd1c5;
            margin-bottom: 5px;
        }
        
        .result-label {
            font-size: 0.9rem;
            color: rgba(255, 255, 255, 0.6);
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            <h2 class="card-title">📝 添加加油记录</h2>
            <form id="fuel-form" class="form-row">
                <div>
                    <label>加油日期</label>
                    <input type="date" id="date" class="form-control" required>
                </div>
                <div>
                    <label>加油量 (升)</label>
                    <input type="number" id="amount" class="form-control" step="0.1" required>
                </div>
                <div>
                    <label>行驶里程 (公里)</label>
                    <input type="number" id="distance" class="form-control" step="0.1" required>
                </div>
                <div>
                    <label>油价 (元/升)</label>
                    <input type="number" id="price" class="form-control" step="0.01">
                </div>
            </form>
            <button class="btn-primary" id="add-btn">添加记录</button>
        </div>
        
        <div class="card">
            <h2 class="card-title">📊 本次计算结果</h2>
            <div class="results">
                <div class="result-card">
                    <div class="result-value" id="fuel-consumption">-</div>
                    <div class="result-label">油耗 (L/100km)</div>
                </div>
                <div class="result-card">
                    <div class="result-value" id="cost-per-km">-</div>
                    <div class="result-label">每公里费用 (元)</div>
                </div>
                <div class="result-card">
                    <div class="result-value" id="total-cost">-</div>
                    <div class="result-label">加油费用 (元)</div>
                </div>
                <div class="result-card">
                    <div class="result-value" id="cost-per-100km">-</div>
                    <div class="result-label">每百公里费用 (元)</div>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

设计要点:

  1. 深色主题设计:使用深蓝渐变背景,营造专业的工具氛围
  2. 毛玻璃效果:通过backdrop-filter: blur(10px)实现现代感
  3. 响应式布局:使用CSS Grid实现自适应列数
  4. 表单样式:输入框使用半透明背景,聚焦时有绿色边框高亮
  5. 结果展示:卡片式布局展示计算结果,绿色高亮数值

3.2 FuelCalculator核心类实现

这是整个应用的核心控制器,负责管理数据、计算和UI更新。

class FuelCalculator {
    constructor() {
        this.records = this.loadRecords();
        this.editIndex = null;
        
        this.dateInput = document.getElementById('date');
        this.amountInput = document.getElementById('amount');
        this.distanceInput = document.getElementById('distance');
        this.priceInput = document.getElementById('price');
        this.addBtn = document.getElementById('add-btn');
        
        this.dateInput.value = new Date().toISOString().split('T')[0];
        
        this.setupEventListeners();
        this.updateUI();
    }
    
    setupEventListeners() {
        this.addBtn.addEventListener('click', () => this.addRecord());
        
        this.amountInput.addEventListener('input', () => this.calculateCurrent());
        this.distanceInput.addEventListener('input', () => this.calculateCurrent());
        this.priceInput.addEventListener('input', () => this.calculateCurrent());
    }
}

核心属性说明:

属性 类型 说明
records Array 加油记录数组
editIndex Number/null 当前编辑的记录索引
dateInput HTMLInputElement 日期输入框
amountInput HTMLInputElement 加油量输入框
distanceInput HTMLInputElement 里程输入框
priceInput HTMLInputElement 油价输入框

3.3 油耗计算核心算法

实时计算油耗和费用的核心方法。

calculateCurrent() {
    const amount = parseFloat(this.amountInput.value);
    const distance = parseFloat(this.distanceInput.value);
    const price = parseFloat(this.priceInput.value) || 0;
    
    if (isNaN(amount) || isNaN(distance) || amount <= 0 || distance <= 0) {
        this.clearResults();
        return;
    }
    
    const consumption = (amount / distance * 100).toFixed(2);
    const totalCost = price > 0 ? (amount * price).toFixed(2) : '-';
    const costPerKm = price > 0 ? ((amount * price) / distance).toFixed(4) : '-';
    const costPer100Km = price > 0 ? (consumption * price).toFixed(2) : '-';
    
    document.getElementById('fuel-consumption').textContent = consumption;
    document.getElementById('cost-per-km').textContent = costPerKm;
    document.getElementById('total-cost').textContent = totalCost;
    document.getElementById('cost-per-100km').textContent = costPer100Km;
}

计算逻辑:

  1. 输入验证:检查加油量和里程是否为有效正数
  2. 油耗计算(加油量 / 行驶里程) × 100,保留2位小数
  3. 费用计算:如果油价未填写,费用显示为-
  4. 实时更新:输入变化时立即重新计算并更新UI

数学公式详解:

油耗(L/100km) = (加油量(L) / 行驶里程(km)) × 100

例如:加油45.5升,行驶520公里
油耗 = (45.5 / 520) × 100 = 8.75 L/100km

每公里费用(元) = 加油费用(元) / 行驶里程(km)
每百公里费用(元) = 油耗(L/100km) × 油价(元/L)

3.4 添加记录方法

addRecord() {
    const date = this.dateInput.value;
    const amount = parseFloat(this.amountInput.value);
    const distance = parseFloat(this.distanceInput.value);
    const price = parseFloat(this.priceInput.value) || 0;
    
    if (!date || isNaN(amount) || isNaN(distance) || amount <= 0 || distance <= 0) {
        this.showToast('请填写正确的加油信息');
        return;
    }
    
    const consumption = (amount / distance * 100).toFixed(2);
    const totalCost = (amount * price).toFixed(2);
    const costPerKm = price > 0 ? (totalCost / distance).toFixed(4) : '-';
    const costPer100Km = price > 0 ? (consumption * price).toFixed(2) : '-';
    
    const record = {
        id: Date.now(),
        date,
        amount: amount.toFixed(1),
        distance: distance.toFixed(1),
        price: price.toFixed(2),
        consumption,
        totalCost,
        costPerKm,
        costPer100Km
    };
    
    if (this.editIndex !== null) {
        this.records[this.editIndex] = record;
        this.showToast('记录已更新');
        this.editIndex = null;
        this.addBtn.innerHTML = '<span>➕</span> 添加记录';
    } else {
        this.records.unshift(record);
        this.showToast('记录已添加');
    }
    
    this.saveRecords();
    this.clearForm();
    this.updateUI();
}

实现逻辑:

  1. 输入验证:确保日期、加油量、里程有效
  2. 计算指标:计算油耗、费用等所有指标
  3. 创建记录对象:包含所有输入值和计算结果
  4. 编辑模式判断:如果是编辑模式,更新现有记录;否则添加新记录
  5. 保存数据:调用saveRecords()持久化到localStorage
  6. 更新UI:刷新历史记录、统计和图表

3.5 统计分析模块

计算累计统计数据和平均值。

updateStats() {
    const totalCount = this.records.length;
    const totalAmount = this.records.reduce((sum, r) => sum + parseFloat(r.amount), 0);
    const totalDistance = this.records.reduce((sum, r) => sum + parseFloat(r.distance), 0);
    
    document.getElementById('total-count').textContent = totalCount;
    document.getElementById('total-amount').textContent = totalAmount.toFixed(1);
    document.getElementById('total-distance').textContent = totalDistance.toFixed(0);
    
    if (totalCount > 0 && totalDistance > 0) {
        const avgConsumption = (totalAmount / totalDistance * 100).toFixed(2);
        document.getElementById('avg-consumption').textContent = `${avgConsumption} <span class="stat-unit">L/100km</span>`;
    } else {
        document.getElementById('avg-consumption').innerHTML = '-';
    }
}

统计指标:

指标 计算方式 说明
总加油次数 records.length 记录总数
累计加油量 sum(amount) 所有记录加油量之和
累计行驶里程 sum(distance) 所有记录里程之和
平均油耗 (累计加油量 / 累计里程) × 100 总油耗除以总里程

3.6 趋势图表生成

使用CSS动态生成柱状图展示油耗趋势。

updateChart() {
    if (this.records.length < 2) {
        document.getElementById('chart-bars').innerHTML = '<div style="text-align: center; color: rgba(255,255,255,0.4); padding: 40px;">需要至少2条记录才能显示趋势</div>';
        document.getElementById('chart-labels').innerHTML = '';
        return;
    }
    
    const recentRecords = this.records.slice(0, Math.min(12, this.records.length)).reverse();
    const consumptions = recentRecords.map(r => parseFloat(r.consumption));
    const maxValue = Math.max(...consumptions) * 1.1;
    const minValue = Math.min(...consumptions) * 0.9;
    const range = maxValue - minValue || 1;
    
    const bars = recentRecords.map((record, index) => {
        const value = parseFloat(record.consumption);
        const height = ((value - minValue) / range) * 80 + 10;
        return `<div class="chart-bar" style="height: ${height}%" data-value="${value.toFixed(2)}"></div>`;
    }).join('');
    
    const labels = recentRecords.map(r => {
        const date = new Date(r.date);
        return `${date.getMonth() + 1}/${date.getDate()}`;
    }).join('<span></span>');
    
    document.getElementById('chart-bars').innerHTML = bars;
    document.getElementById('chart-labels').innerHTML = `<span>${labels}</span>`;
}

图表生成逻辑:

┌────────────────────────────────────────────────────┐
│ 柱状图生成流程                                      │
├────────────────────────────────────────────────────┤
│ 1. 数据准备                                        │
│    - 取最近12条记录(按时间倒序)                     │
│    - 反转数组,按时间正序排列                        │
│                                                    │
│ 2. 数值标准化                                       │
│    - 找出最大值和最小值                             │
│    - 计算范围:maxValue * 1.1 - minValue * 0.9      │
│    - 每个值映射到10%-90%的高度范围                   │
│                                                    │
│ 3. DOM生成                                         │
│    - 动态创建chart-bar元素                          │
│    - 设置data-value属性用于悬停显示                  │
│    - 生成日期标签                                   │
└────────────────────────────────────────────────────┘

技术要点:

  1. 数据范围限制:最多显示最近12条记录,避免图表过于拥挤
  2. 数值标准化:将油耗值映射到10%-90%的高度范围,确保柱状图有足够的视觉差异
  3. 响应式高度:使用百分比高度,适配不同容器大小
  4. 悬停提示:通过data-value属性存储原始值,配合CSS伪元素显示

3.7 数据持久化

使用localStorage实现数据的本地持久化存储。

saveRecords() {
    localStorage.setItem('fuelRecords', JSON.stringify(this.records));
}

loadRecords() {
    const saved = localStorage.getItem('fuelRecords');
    return saved ? JSON.parse(saved) : [];
}

存储设计:

存储键 数据格式 说明
fuelRecords JSON数组 加油记录列表

优势:

  • 无需后端服务器
  • 数据保存在用户本地
  • 简单易用,适合轻量级应用
  • 自动持久化,刷新页面不丢失

3.8 记录管理(编辑和删除)

editRecord(index) {
    const record = this.records[index];
    this.dateInput.value = record.date;
    this.amountInput.value = record.amount;
    this.distanceInput.value = record.distance;
    this.priceInput.value = record.price;
    this.editIndex = index;
    this.addBtn.innerHTML = '<span>✏️</span> 更新记录';
    
    window.scrollTo({ top: 0, behavior: 'smooth' });
    this.calculateCurrent();
}

deleteRecord(index) {
    this.records.splice(index, 1);
    this.saveRecords();
    this.updateUI();
    this.showToast('记录已删除');
}

编辑流程:

  1. 填充表单为选中记录的数据
  2. 设置editIndex标记为编辑模式
  3. 更改按钮文字为"更新记录"
  4. 滚动到页面顶部
  5. 重新计算当前值

删除流程:

  1. 使用splice从数组中移除记录
  2. 保存更新后的数据
  3. 更新UI显示
  4. 显示操作提示

4. 用户交互设计

4.1 表单验证

if (!date || isNaN(amount) || isNaN(distance) || amount <= 0 || distance <= 0) {
    this.showToast('请填写正确的加油信息');
    return;
}

验证规则:

  • 日期必须填写
  • 加油量必须是大于0的数字
  • 行驶里程必须是大于0的数字
  • 油价可选,不填则费用显示为-

4.2 Toast提示

showToast(message) {
    const toast = document.getElementById('toast');
    toast.textContent = message;
    toast.classList.add('show');
    setTimeout(() => {
        toast.classList.remove('show');
    }, 2000);
}

Toast样式:

.toast {
    position: fixed;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.85);
    color: #4fd1c5;
    padding: 15px 30px;
    border-radius: 10px;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s ease;
}

.toast.show {
    opacity: 1;
    visibility: visible;
}

4.3 清空确认弹窗

this.clearAllBtn.addEventListener('click', () => {
    this.modalOverlay.classList.add('show');
});

this.confirmClearBtn.addEventListener('click', () => {
    this.clearAllRecords();
    this.modalOverlay.classList.remove('show');
});

安全设计:

  • 清空操作需要二次确认
  • 弹窗显示警告信息
  • 防止误操作导致数据丢失

5. 鸿蒙PC平台适配

5.1 窗口配置

const { app, BrowserWindow } = require('electron');

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 900,
        height: 1000,
        minWidth: 600,
        minHeight: 800,
        title: '油耗计算器',
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
        },
    });

    mainWindow.loadFile('fuel_calculator.html');
}

app.on('ready', createWindow);

5.2 平台特殊考虑

  1. 窗口尺寸:针对鸿蒙PC设备优化默认窗口大小(900x1000)
  2. 字体渲染:确保中文和数字在鸿蒙平台正确显示
  3. 响应式设计:使用CSS Grid实现自适应布局
  4. 触摸支持:按钮和输入框尺寸适合触摸操作

6. 代码优化建议

6.1 性能优化

// 当前实现:每次输入变化都更新UI
// 优化方案:使用防抖减少计算频率

class FuelCalculator {
    constructor() {
        this.debounceTimer = null;
        // ...
    }
    
    calculateCurrent() {
        clearTimeout(this.debounceTimer);
        this.debounceTimer = setTimeout(() => {
            // 实际计算逻辑
        }, 300);
    }
}

6.2 数据验证增强

// 添加更严格的数据验证
validateInput(date, amount, distance, price) {
    const errors = [];
    
    if (!date) {
        errors.push('请选择加油日期');
    }
    
    if (isNaN(amount) || amount <= 0 || amount > 1000) {
        errors.push('加油量必须在0-1000升之间');
    }
    
    if (isNaN(distance) || distance <= 0 || distance > 10000) {
        errors.push('行驶里程必须在0-10000公里之间');
    }
    
    if (price && (isNaN(price) || price <= 0 || price > 30)) {
        errors.push('油价必须在0-30元之间');
    }
    
    return errors;
}

6.3 图表优化

// 添加平滑动画效果
updateChart() {
    // ... 生成bars HTML
    
    const chartBars = document.getElementById('chart-bars');
    chartBars.innerHTML = bars;
    
    // 添加过渡动画
    setTimeout(() => {
        document.querySelectorAll('.chart-bar').forEach(bar => {
            bar.style.transition = 'height 0.5s ease-out';
        });
    }, 100);
}

7. 总结

本文详细介绍了基于Electron框架在鸿蒙PC平台上实现油耗计算器的完整方案。核心技术点包括:

  1. 油耗计算算法:基于加油量和行驶里程计算油耗,支持实时计算
  2. 统计分析:累计加油量、累计里程、平均油耗等统计指标
  3. 趋势图表:使用CSS动态生成柱状图,展示油耗变化趋势
  4. 数据持久化:使用localStorage实现本地数据存储
  5. 记录管理:支持添加、编辑、删除和清空操作

该实现不仅提供了实用的油耗管理功能,还具备良好的用户体验和现代化的UI设计,适合作为学习桌面应用开发和数据可视化的参考项目。


附录:完整文件清单

文件路径 说明
fuel_calculator.html 主HTML页面
fuel_calculator.js 核心JavaScript逻辑
home.html 应用中心入口(包含导航链接)
main.js Electron主进程配置
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐