主要改进: - 新增配置管理 (server/config.js) - 新增日志工具 (server/utils/logger.js) - 添加全局错误处理中间件,统一错误响应格式 - 添加输入验证和 AppError 错误类 - 重构 db.js 迁移逻辑,代码更清晰 - 前端列表改为无限滚动加载,每页 20 条 - 封装 AppState 状态管理模块 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
5.7 KiB
JavaScript
180 lines
5.7 KiB
JavaScript
const express = require('express');
|
|
const db = require('../db');
|
|
const config = require('../config');
|
|
const logger = require('../utils/logger');
|
|
const { getMonthRange, AppError, validateRequired, validateNumber, validateInList } = require('../utils/helpers');
|
|
|
|
const router = express.Router();
|
|
|
|
// 新增记录
|
|
router.post('/', (req, res, next) => {
|
|
try {
|
|
const { type, category, amount, quantity, unit_price, rebate, boss, partner, source, destination, note, created_at } = req.body;
|
|
|
|
// 输入验证
|
|
validateRequired(req.body, ['type', 'category', 'amount']);
|
|
validateInList(type, 'type', ['income', 'expense']);
|
|
|
|
const allCategories = [...config.categories.income, ...config.categories.expense];
|
|
validateInList(category, 'category', allCategories);
|
|
|
|
const validatedAmount = validateNumber(amount, '金额', 0);
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO records (user_id, type, category, amount, quantity, unit_price, rebate, boss, partner, source, destination, note, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
const result = stmt.run(req.user.id, type, category, validatedAmount, quantity || null, unit_price || null, rebate || null, boss || null, partner || null, source || null, destination || null, note || null, created_at || new Date().toISOString());
|
|
|
|
logger.info(`用户 ${req.user.username} 新增记录: ${category} ${validatedAmount}`);
|
|
res.json({ id: result.lastInsertRowid });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
// 通用分页查询函数
|
|
function queryPagedRecords(userId, year, month, options) {
|
|
const { page = 1, limit = 20 } = options;
|
|
const offset = (Number(page) - 1) * Number(limit);
|
|
const { startDate, endDate } = getMonthRange(year, month);
|
|
|
|
let whereClause = `user_id = ? AND DATE(created_at, 'localtime') >= ? AND DATE(created_at, 'localtime') < ?`;
|
|
const params = [userId, startDate, endDate];
|
|
|
|
if (options.category) {
|
|
whereClause += ` AND category = ?`;
|
|
params.push(options.category);
|
|
}
|
|
if (options.type) {
|
|
whereClause += ` AND type = ?`;
|
|
params.push(options.type);
|
|
}
|
|
if (options.excludeCategories && options.excludeCategories.length > 0) {
|
|
whereClause += ` AND category NOT IN (${options.excludeCategories.map(() => '?').join(',')})`;
|
|
params.push(...options.excludeCategories);
|
|
}
|
|
|
|
const total = db.prepare(
|
|
`SELECT COUNT(*) as count FROM records WHERE ${whereClause}`
|
|
).get(...params).count;
|
|
|
|
const records = db.prepare(
|
|
`SELECT * FROM records WHERE ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`
|
|
).all(...params, Number(limit), offset);
|
|
|
|
return { records, total, page: Number(page), limit: Number(limit) };
|
|
}
|
|
|
|
// 接单列表
|
|
router.get('/orders', (req, res) => {
|
|
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
|
|
category: '接单',
|
|
page: req.query.page,
|
|
limit: req.query.limit
|
|
});
|
|
res.json(result);
|
|
});
|
|
|
|
// 红包及其他收入列表
|
|
router.get('/other-income', (req, res) => {
|
|
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
|
|
type: 'income',
|
|
excludeCategories: ['接单', '派单'],
|
|
page: req.query.page,
|
|
limit: req.query.limit
|
|
});
|
|
res.json(result);
|
|
});
|
|
|
|
// 红包收入列表
|
|
router.get('/red-envelope', (req, res) => {
|
|
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
|
|
type: 'income',
|
|
category: '红包收入',
|
|
page: req.query.page,
|
|
limit: req.query.limit
|
|
});
|
|
res.json(result);
|
|
});
|
|
|
|
// 奶茶收入列表
|
|
router.get('/milk-tea', (req, res) => {
|
|
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
|
|
type: 'income',
|
|
category: '奶茶',
|
|
page: req.query.page,
|
|
limit: req.query.limit
|
|
});
|
|
res.json(result);
|
|
});
|
|
|
|
// 派单列表
|
|
router.get('/dispatches', (req, res) => {
|
|
const result = queryPagedRecords(req.user.id, req.query.year, req.query.month, {
|
|
category: '派单',
|
|
page: req.query.page,
|
|
limit: req.query.limit
|
|
});
|
|
res.json(result);
|
|
});
|
|
|
|
// 获取记录列表
|
|
router.get('/', (req, res) => {
|
|
const { type, page = 1, limit = 20 } = req.query;
|
|
const offset = (page - 1) * limit;
|
|
let sql = 'SELECT * FROM records WHERE user_id = ?';
|
|
const params = [req.user.id];
|
|
if (type) {
|
|
sql += ' AND type = ?';
|
|
params.push(type);
|
|
}
|
|
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
|
params.push(Number(limit), Number(offset));
|
|
res.json(db.prepare(sql).all(...params));
|
|
});
|
|
|
|
// 更新记录
|
|
router.put('/:id', (req, res, next) => {
|
|
try {
|
|
const { quantity, unit_price, rebate, amount, boss, partner, source, destination, note, created_at } = req.body;
|
|
const id = Number(req.params.id);
|
|
|
|
if (!id || id <= 0) {
|
|
throw new AppError('无效的记录ID', 400);
|
|
}
|
|
|
|
const validatedAmount = validateNumber(amount, '金额', 0);
|
|
|
|
db.prepare(`
|
|
UPDATE records SET quantity = ?, unit_price = ?, rebate = ?, amount = ?, boss = ?, partner = ?, source = ?, destination = ?, note = ?, created_at = ?
|
|
WHERE id = ? AND user_id = ?
|
|
`).run(quantity || null, unit_price || null, rebate || null, validatedAmount, boss || null, partner || null, source || null, destination || null, note || null, created_at || null, id, req.user.id);
|
|
|
|
logger.debug(`用户 ${req.user.username} 更新记录: ${id}`);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
// 删除记录
|
|
router.delete('/:id', (req, res, next) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
|
|
if (!id || id <= 0) {
|
|
throw new AppError('无效的记录ID', 400);
|
|
}
|
|
|
|
db.prepare('DELETE FROM records WHERE id = ? AND user_id = ?').run(id, req.user.id);
|
|
|
|
logger.info(`用户 ${req.user.username} 删除记录: ${id}`);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|