本文目录导读:

实现PHP账单查询功能,核心在于数据库设计、查询逻辑(按时间、类型、关键词等筛选)以及前端展示(分页、排序),以下是分步实现指南。
数据库设计(MySQL)
假设数据表 bills 用于存储账单记录:
CREATE TABLE `bills` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL COMMENT '用户ID',
`category` VARCHAR(50) NOT NULL COMMENT '分类(餐饮/交通/购物等)',
`type` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '0支出 1收入',
`amount` DECIMAL(10,2) NOT NULL COMMENT '金额',
`note` VARCHAR(255) DEFAULT NULL COMMENT '备注',
`billing_date` DATE NOT NULL COMMENT '账单日期',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_date` (`user_id`, `billing_date`) -- 复合索引加速查询
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键点:
- 使用
billing_date而非created_at作为账单日期,方便按自然日查询。- 建立
(user_id, billing_date)复合索引,这是查询最常用的条件。- 金额字段使用
DECIMAL(10,2)而非FLOAT,避免精度问题。
查询功能实现(PHP + PDO)
1 查询表单(HTML)
放在 index.php 或 search.php:
<form method="get" action="query_bills.php">
<input type="text" name="keyword" placeholder="备注关键词" value="<?= htmlspecialchars($_GET['keyword'] ?? '') ?>">
<select name="category">
<option value="">所有分类</option>
<option value="餐饮" <?= ($_GET['category'] ?? '') == '餐饮' ? 'selected' : '' ?>>餐饮</option>
<option value="交通" <?= ($_GET['category'] ?? '') == '交通' ? 'selected' : '' ?>>交通</option>
<option value="购物" <?= ($_GET['category'] ?? '') == '购物' ? 'selected' : '' ?>>购物</option>
</select>
<input type="date" name="start_date" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
<input type="date" name="end_date" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
<button type="submit">查询</button>
</form>
2 后台查询逻辑(query_bills.php)
<?php
// 1. 数据库连接(以 PDO 为例)
$pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 2. 安全获取查询参数
$params = [];
$conditions = ['user_id = :user_id']; // 假设当前登录用户ID = 1
$params[':user_id'] = 1;
// --- 按日期范围筛选 ---
if (!empty($_GET['start_date'])) {
$conditions[] = 'billing_date >= :start_date';
$params[':start_date'] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$conditions[] = 'billing_date <= :end_date';
$params[':end_date'] = $_GET['end_date'];
}
// --- 按分类筛选 ---
if (!empty($_GET['category'])) {
$conditions[] = 'category = :category';
$params[':category'] = $_GET['category'];
}
// --- 按备注关键词模糊搜索 ---
if (!empty($_GET['keyword'])) {
$conditions[] = 'note LIKE :keyword';
$params[':keyword'] = '%' . $_GET['keyword'] . '%';
}
// --- 按类型筛选(收入/支出)---
if (isset($_GET['type']) && $_GET['type'] !== '') {
$conditions[] = 'type = :type';
$params[':type'] = (int)$_GET['type'];
}
// 3. 组装SQL并执行
$sql = 'SELECT * FROM bills WHERE ' . implode(' AND ', $conditions);
$sql .= ' ORDER BY billing_date DESC, created_at DESC'; // 默认按日期倒序
// 分页支持(可选)
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 20;
$offset = ($page - 1) * $perPage;
// 先查询总数用于分页
$countSql = str_replace('SELECT *', 'SELECT COUNT(*)', $sql);
$stmt = $pdo->prepare($countSql);
$stmt->execute($params);
$total = $stmt->fetchColumn();
// 再查询具体数据
$sql .= " LIMIT $perPage OFFSET $offset";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$bills = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 4. 处理结果(示例:计算总收入和总支出)
$totalIncome = $totalExpense = 0;
foreach ($bills as $bill) {
if ($bill['type'] == 1) $totalIncome += $bill['amount'];
else $totalExpense += $bill['amount'];
}
// 5. 输出结果(可放到HTML模板中)
?>
前端展示(分页 + 统计)
<?php if (!empty($bills)): ?>
<p>共 <?= $total ?> 条记录,收入总额:<?= $totalIncome ?>,支出总额:<?= $totalExpense ?></p>
<table>
<tr><th>日期</th><th>分类</th><th>类型</th><th>金额</th><th>备注</th></tr>
<?php foreach ($bills as $bill): ?>
<tr>
<td><?= htmlspecialchars($bill['billing_date']) ?></td>
<td><?= htmlspecialchars($bill['category']) ?></td>
<td><?= $bill['type'] == 1 ? '收入' : '支出' ?></td>
<td style="color:<?= $bill['type'] == 1 ? 'green' : 'red' ?>"><?= number_format($bill['amount'], 2) ?></td>
<td><?= htmlspecialchars($bill['note'] ?? '') ?></td>
</tr>
<?php endforeach; ?>
</table>
<!-- 简单分页(保持当前查询条件) -->
<?php
$totalPages = ceil($total / $perPage);
$queryParams = $_GET;
for ($i = 1; $i <= $totalPages; $i++):
$queryParams['page'] = $i;
$queryString = http_build_query($queryParams);
?>
<a href="?<?= $queryString ?>" <?= $i == $page ? 'style="font-weight:bold"' : '' ?>><?= $i ?></a>
<?php endfor; ?>
<?php else: ?>
<p>暂无账单记录</p>
<?php endif; ?>
扩展高级功能
-
分组统计:按分类统计金额
SELECT category, SUM(amount) AS total FROM bills WHERE ... GROUP BY category ORDER BY total DESC
-
月度对比:按月统计收支
SELECT DATE_FORMAT(billing_date, '%Y-%m') AS month, SUM(CASE WHEN type=1 THEN amount ELSE 0 END) AS income, SUM(CASE WHEN type=0 THEN amount ELSE 0 END) AS expense FROM bills WHERE ... GROUP BY month ORDER BY month DESC -
导出CSV:
header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="bills.csv"'); $output = fopen('php://output', 'w'); fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF)); // BOM for Excel fputcsv($output, ['日期','分类','金额','备注']); foreach ($bills as $row) { fputcsv($output, [$row['billing_date'], $row['category'], $row['amount'], $row['note']]); } fclose($output); -
安全建议:
- 使用 预处理语句(Prepared Statement) 防止SQL注入(上面已使用)。
- 用户ID必须从Session获取,不能从客户端提交。
- 对输出做
htmlspecialchars()防止XSS。 - 如果数据量大(>10万条),建议加上 分页 和 合理索引,避免全表扫描。
完整代码结构示例
project/
├── index.php # 表单输入页
├── query_bills.php # 查询逻辑 + 结果展示(或分离为两个文件)
├── db.php # 数据库连接配置
└── style.css # 样式(可选)
如果希望分离逻辑,query_bills.php 仅输出JSON,前端用JavaScript(Vue/React)渲染,但小项目使用服务端渲染更简单直接。
如果需要更具体的实现(如ThinkPHP/Laravel框架版本、微信小程序对接、图表统计等),可以进一步说明。