实现一个简单的记账应用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();
});