前端开发:简单的记账应用2.0

实现一个简单的记账应用2.0:

在现代生活中,记账是管理个人财务的基本技能。今天,我们将学习如何用 HTML、CSS 和 JavaScript 创建一个简单的记账应用,其中包括自动添加时间戳和页面美化的功能。通过这篇文章,你将学会如何使用这些技术来开发一个实用的记账工具,并使其更具吸引力。对比上一个添加了新的一些功能

项目结构

本项目包括三个主要文件:

  • index.html:定义页面结构
  • style.css:设置页面样式
  • script.js:实现功能逻辑

1. HTML 文件

首先,我们创建一个基本的 HTML 文件,用于构建应用的界面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>记账应用</title>
    <!-- 链接到 CSS 文件以应用样式 -->
    <link rel="stylesheet" href="style.css">
    <!-- 引入 Chart.js 库 -->
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <div class="container">
        <h1>记账应用</h1>
        
        <!-- 输入描述 -->
        <input type="text" id="description" placeholder="描述">
        <!-- 输入金额 -->
        <input type="number" id="amount" placeholder="金额">
        <!-- 输入分类 -->
        <input type="text" id="category" placeholder="分类">
        <!-- 输入预算 -->
        <input type="number" id="budget" placeholder="预算">
        <!-- 添加记录按钮 -->
        <button id="add-btn">添加记录</button>
        <!-- 显示预算 -->
        <div id="budget-display">预算:0元,剩余:0元</div>
        <!-- 搜索框 -->
        <input type="text" id="search" placeholder="搜索记录">
        <!-- 分类筛选 -->
        <select id="filter-category">
            <option value="">所有分类</option>
            <option value="食品">食品</option>
            <option value="交通">交通</option>
            <option value="娱乐">娱乐</option>
            <option value="其他">其他</option>
        </select>
        <!-- 显示记录的列表 -->
        <ul id="record-list"></ul>
        <!-- 导出按钮 -->
        <button id="export-btn">导出数据</button>
        <!-- 图表展示 -->
        <div id="chart-container">
            <canvas id="expense-chart"></canvas>
        </div>
    </div>
    <!-- 链接到 JavaScript 文件以实现功能 -->
    <script src="script.js"></script>
</body>
</html>

2. CSS 文件

接下来,我们使用 CSS 文件来美化页面。

/* 通用样式 */
body, h1, input, button, ul {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* 设置页面背景和对齐方式 */
body {
    font-family: Arial, sans-serif; /* 字体 */
    background-color: #f4f4f4; /* 背景颜色 */
    display: flex; /* 使用 Flexbox 布局 */
    justify-content: center; /* 水平居中对齐 */
    align-items: center; /* 垂直居中对齐 */
    height: 100vh; /* 高度为视口高度 */
}

/* 设置容器样式 */
.container {
    background: #fff; /* 背景颜色 */
    border-radius: 8px; /* 圆角 */
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* 阴影 */
    padding: 20px; /* 内边距 */
    width: 400px; /* 宽度 */
    text-align: center; /* 文本居中 */
}

/* 标题样式 */
h1 {
    color: #333; /* 字体颜色 */
    margin-bottom: 20px; /* 底部外边距 */
}

/* 输入框样式 */
input {
    width: calc(50% - 10px); /* 宽度,减去边距 */
    padding: 10px; /* 内边距 */
    border: 1px solid #ddd; /* 边框 */
    border-radius: 4px; /* 圆角 */
    font-size: 16px; /* 字体大小 */
    margin-bottom: 10px; /* 底部外边距 */
    display: inline-block; /* 使其与按钮对齐 */
}

/* 数字输入框样式 */
input[type="number"] {
    margin-left: 10px; /* 左侧外边距 */
}

/* 分类输入框样式 */
input[type="text"]#category {
    margin-top: 10px; /* 顶部外边距 */
}

/* 预算输入框样式 */
input[type="number"]#budget {
    margin-top: 10px; /* 顶部外边距 */
}

/* 添加按钮样式 */
#add-btn {
    width: 100%; /* 宽度 */
    padding: 10px; /* 内边距 */
    background-color: #28a745; /* 背景颜色 */
    color: #fff; /* 字体颜色 */
    border: none; /* 去除边框 */
    border-radius: 4px; /* 圆角 */
    font-size: 16px; /* 字体大小 */
    cursor: pointer; /* 鼠标指针样式 */
    transition: background-color 0.3s; /* 背景颜色过渡效果 */
    margin-bottom: 10px; /* 底部外边距 */
}

/* 按钮悬停效果 */
#add-btn:hover {
    background-color: #218838; /* 背景颜色变化 */
}

/* 导出按钮样式 */
#export-btn {
    width: 100%; /* 宽度 */
    padding: 10px; /* 内边距 */
    background-color: #007bff; /* 背景颜色 */
    color: #fff; /* 字体颜色 */
    border: none; /* 去除边框 */
    border-radius: 4px; /* 圆角 */
    font-size: 16px; /* 字体大小 */
    cursor: pointer; /* 鼠标指针样式 */
    transition: background-color 0.3s; /* 背景颜色过渡效果 */
}

/* 导出按钮悬停效果 */
#export-btn:hover {
    background-color: #0056b3; /* 背景颜色变化 */
}

/* 显示预算样式 */
#budget-display {
    margin: 10px 0; /* 外边距 */
    font-size: 16px; /* 字体大小 */
}

/* 搜索框样式 */
#search {
    width: 100%; /* 宽度 */
    padding: 10px; /* 内边距 */
    border: 1px solid #ddd; /* 边框 */
    border-radius: 4px; /* 圆角 */
    font-size: 16px; /* 字体大小 */
    margin-bottom: 10px; /* 底部外边距 */
}

/* 分类筛选样式 */
#filter-category {
    width: 100%; /* 宽度 */
    padding: 10px; /* 内边距 */
    border: 1px solid #ddd; /* 边框 */
    border-radius: 4px; /* 圆角 */
    font-size: 16px; /* 字体大小 */
    margin-bottom: 10px; /* 底部外边距 */
}

/* 显示记录的列表 */
#record-list {
    list-style-type: none; /* 去除列表项标记 */
    padding: 0; /* 去除内边距 */
    margin-top: 20px; /* 顶部外边距 */
}

/* 记录项样式 */
#record-list li {
    background: #f9f9f9; /* 背景颜色 */
    border-bottom: 1px solid #ddd; /* 底部边框 */
    padding: 10px; /* 内边距 */
    font-size: 16px; /* 字体大小 */
    display: flex; /* 使用 Flexbox 布局 */
    justify-content: space-between; /* 两端对齐 */
    align-items: center; /* 垂直居中对齐 */
}

/* 记录文本样式 */
.record-text {
    flex: 1; /* 占据剩余空间 */
}

/* 时间样式 */
.time {
    color: #888; /* 字体颜色 */
    font-size: 14px; /* 字体大小 */
    margin-left: 10px; /* 左侧外边距 */
}

/* 删除按钮样式 */
.remove-btn {
    background-color: #dc3545; /* 背景颜色 */
    color: #fff; /* 字体颜色 */
    border: none; /* 去除边框 */
    border-radius: 4px; /* 圆角 */
    padding: 5px 10px; /* 内边距 */
    font-size: 14px; /* 字体大小 */
    cursor: pointer; /* 鼠标指针样式 */
    transition: background-color 0.3s; /* 背景颜色过渡效果 */
}

/* 删除按钮悬停效果 */
.remove-btn:hover {
    background-color: #c82333; /* 背景颜色变化 */
}

/* 编辑按钮样式 */
.edit-btn {
    background-color: #ffc107; /* 背景颜色 */
    color: #fff; /* 字体颜色 */
    border: none; /* 去除边框 */
    border-radius: 4px; /* 圆角 */
    padding: 5px 10px; /* 内边距 */
    font-size: 14px; /* 字体大小 */
    cursor: pointer; /* 鼠标指针样式 */
    margin: 0 5px; /* 左右外边距 */
    transition: background-color 0.3s; /* 背景颜色过渡效果 */
}

/* 编辑按钮悬停效果 */
.edit-btn:hover {
    background-color: #e0a800; /* 背景颜色变化 */
}

/* 图表容器样式 */
#chart-container {
    margin-top: 20px; /* 顶部外边距 */
}

3. JavaScript 文件

最后,我们实现 JavaScript 文件以处理应用逻辑,包括记录的添加、显示和删除等功能。

document.addEventListener('DOMContentLoaded', () => {
    // 获取 DOM 元素
    const descriptionInput = document.getElementById('description');
    const amountInput = document.getElementById('amount');
    const categoryInput = document.getElementById('category');
    const budgetInput = document.getElementById('budget');
    const searchInput = document.getElementById('search');
    const filterCategory = document.getElementById('filter-category');
    const addButton = document.getElementById('add-btn');
    const exportButton = document.getElementById('export-btn');
    const recordList = document.getElementById('record-list');
    const chartContainer = document.getElementById('chart-container');
    let editIndex = null;

    function updateBudget() {
        const budget = parseFloat(budgetInput.value.trim()) || 0;
        const records = JSON.parse(localStorage.getItem('records')) || [];
        const total = records.reduce((acc, record) => acc + record.amount, 0);
        const remaining = budget - total;

        document.getElementById('budget-display').textContent = `预算:${budget}元,剩余:${remaining}元`;
    }

    function renderRecords() {
        const searchQuery = searchInput.value.trim().toLowerCase();
        const selectedCategory = filterCategory.value;
        const records = JSON.parse(localStorage.getItem('records')) || [];
        recordList.innerHTML = '';

        const filteredRecords = records.filter(record => {
            const matchesSearch = record.description.toLowerCase().includes(searchQuery);
            const matchesCategory = selectedCategory ? record.category === selectedCategory : true;
            return matchesSearch && matchesCategory;
        });

        filteredRecords.forEach((record, index) => {
            const li = document.createElement('li');
            li.innerHTML = `
                <span class="record-text">${record.description} - ${record.amount}元 - ${record.category}</span>
                <span class="time">${record.time}</span>
                <button class="edit-btn">编辑</button>
                <button class="remove-btn">删除</button>
            `;

            const editButton = li.querySelector('.edit-btn');
            const removeButton = li.querySelector('.remove-btn');

            editButton.addEventListener('click', () => {
                descriptionInput.value = record.description;
                amountInput.value = record.amount;
                categoryInput.value = record.category;
                editIndex = index;
            });

            removeButton.addEventListener('click', () => {
                records.splice(index, 1);
                localStorage.setItem('records', JSON.stringify(records));
                renderRecords();
                updateBudget();
            });

            recordList.appendChild(li);
        });

        updateBudget();
        renderChart();
    }

    function renderChart() {
        const records = JSON.parse(localStorage.getItem('records')) || [];
        const categories = ['食品', '交通', '娱乐', '其他'];
        const data = categories.map(cat => 
            records.filter(record => record.category === cat).reduce((acc, record) => acc + record.amount, 0)
        );

        const ctx = document.getElementById('expense-chart').getContext('2d');
        new Chart(ctx, {
            type: 'pie',
            data: {
                labels: categories,
                datasets: [{
                    data: data,
                    backgroundColor: ['#ff6384', '#36a2eb', '#cc65fe', '#ffce56'],
                }]
            },
            options: {
                responsive: true,
                plugins: {
                    legend: {
                        position: 'top',
                    },
                    tooltip: {
                        callbacks: {
                            label: function(context) {
                                let label = context.label || '';
                                if (label) {
                                    label += ': ';
                                }
                                if (context.parsed !== null) {
                                    label += `${context.parsed.toFixed(2)}元`;
                                }
                                return label;
                            }
                        }
                    }
                }
            }
        });
    }

    addButton.addEventListener('click', () => {
        const description = descriptionInput.value.trim();
        const amount = parseFloat(amountInput.value.trim());
        const category = categoryInput.value;

        if (description && !isNaN(amount)) {
            const records = JSON.parse(localStorage.getItem('records')) || [];
            const now = new Date().toLocaleString();

            if (editIndex !== null) {
                records[editIndex] = { description, amount, category, time: now };
                editIndex = null;
            } else {
                records.push({ description, amount, category, time: now });
            }

            localStorage.setItem('records', JSON.stringify(records));
            descriptionInput.value = '';
            amountInput.value = '';
            categoryInput.value = '';
            renderRecords();
        }
    });

    searchInput.addEventListener('input', renderRecords);
    filterCategory.addEventListener('change', renderRecords);

    exportButton.addEventListener('click', () => {
        const records = JSON.parse(localStorage.getItem('records')) || [];
        const csvContent = "data:text/csv;charset=utf-8," 
            + "描述,金额,分类,时间\n" 
            + records.map(record => `${record.description},${record.amount},${record.category},${record.time}`).join("\n");

        const encodedUri = encodeURI(csvContent);
        const link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", "records.csv");
        document.body.appendChild(link);
        link.click();
    });

    renderRecords();
});

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值