feat: 用户信息完善 + 一起记功能
- users 表添加 nickname、avatar_url 字段 - 用户信息 API(GET/PUT /me)+ 头像上传(multer) - 头像通过 API 路由获取(公开,不经过 auth) - 登录接口返回用户昵称和头像 - 用户信息 Store + 个人资料编辑页面 - 我的页面和首页显示真实用户信息 - 群组表(groups、group_members)+ transactions 添加 group_id - 群组 API(创建/加入/退出/解散) - 交易和统计 API 支持 group_id 视图切换 - 群组客户端 Store + 身份切换 - 群组管理页面 - App 初始化群组 Store - UPLOAD_DIR 配置化 - Node.js 备份替代 mysqldump - 统一前端 BASE_URL(config.ts) - uploads 加入 .gitignore - 修复 trust proxy 警告
This commit is contained in:
@@ -6,19 +6,49 @@ import { getCurrentMonth, getMonthRange } from '../utils/date'
|
||||
const router = Router()
|
||||
const VALID_TYPES = ['expense', 'income']
|
||||
|
||||
/** 根据 group_id 参数构建 WHERE 条件(含成员验证) */
|
||||
async function buildWhereClause(
|
||||
userId: number | undefined,
|
||||
groupId: string | undefined,
|
||||
extraConditions: string[] = []
|
||||
): Promise<{ where: string; params: any[] } | null> {
|
||||
const params: any[] = []
|
||||
|
||||
if (groupId && groupId !== 'null') {
|
||||
// 验证用户是群组成员
|
||||
const [memberCheck] = await pool.query(
|
||||
'SELECT id FROM group_members WHERE group_id = ? AND user_id = ?',
|
||||
[groupId, userId]
|
||||
)
|
||||
if ((memberCheck as any[]).length === 0) return null
|
||||
|
||||
params.push(groupId)
|
||||
let where = `WHERE t.user_id IN (SELECT user_id FROM group_members WHERE group_id = ?)`
|
||||
if (extraConditions.length > 0) where += ' AND ' + extraConditions.join(' AND ')
|
||||
return { where, params }
|
||||
}
|
||||
|
||||
params.push(userId)
|
||||
let where = `WHERE t.user_id = ?`
|
||||
if (extraConditions.length > 0) where += ' AND ' + extraConditions.join(' AND ')
|
||||
return { where, params }
|
||||
}
|
||||
|
||||
router.get('/overview', async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { month, startDate: qStart, endDate: qEnd } = req.query
|
||||
const { month, startDate: qStart, endDate: qEnd, group_id } = req.query
|
||||
|
||||
let startDate: string
|
||||
let endDate: string
|
||||
let elapsedDays = 1
|
||||
|
||||
if (qStart && qEnd) {
|
||||
// 按日期范围查询(用于今日等场景)
|
||||
startDate = qStart as string
|
||||
endDate = qEnd as string
|
||||
elapsedDays = 1
|
||||
// 计算日期范围天数
|
||||
const start = new Date(startDate)
|
||||
const end = new Date(endDate)
|
||||
elapsedDays = Math.max(1, Math.ceil((end.getTime() - start.getTime()) / 86400000) + 1)
|
||||
} else {
|
||||
const m = (month as string) || getCurrentMonth()
|
||||
const range = getMonthRange(m)
|
||||
@@ -31,13 +61,20 @@ router.get('/overview', async (req: AuthRequest, res: Response) => {
|
||||
elapsedDays = isCurrentMonth ? now.getDate() : new Date(year, mon, 0).getDate()
|
||||
}
|
||||
|
||||
const result = await buildWhereClause(
|
||||
req.userId,
|
||||
group_id as string | undefined,
|
||||
['t.date >= ?', 't.date <= ?']
|
||||
)
|
||||
if (!result) return res.status(403).json({ code: 40300, message: '无权访问此群组' })
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`SELECT
|
||||
COALESCE(SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END), 0) as expense,
|
||||
COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END), 0) as income,
|
||||
COALESCE(SUM(CASE WHEN t.type = 'expense' THEN t.amount ELSE 0 END), 0) as expense,
|
||||
COALESCE(SUM(CASE WHEN t.type = 'income' THEN t.amount ELSE 0 END), 0) as income,
|
||||
COUNT(*) as count
|
||||
FROM transactions WHERE user_id = ? AND date >= ? AND date <= ?`,
|
||||
[req.userId, startDate, endDate]
|
||||
FROM transactions t ${result.where}`,
|
||||
[...result.params, startDate, endDate]
|
||||
)
|
||||
const row = (rows as any[])[0]
|
||||
const daily = elapsedDays > 0 ? Math.round(row.expense / elapsedDays) : 0
|
||||
@@ -54,7 +91,7 @@ router.get('/overview', async (req: AuthRequest, res: Response) => {
|
||||
|
||||
router.get('/category', async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { month, type = 'expense' } = req.query
|
||||
const { month, type = 'expense', group_id } = req.query
|
||||
if (!VALID_TYPES.includes(type as string)) {
|
||||
return res.status(400).json({ code: 40002, message: '类型无效' })
|
||||
}
|
||||
@@ -63,14 +100,21 @@ router.get('/category', async (req: AuthRequest, res: Response) => {
|
||||
if (!range) return res.status(400).json({ code: 40001, message: '月份格式无效' })
|
||||
const { startDate, endDate } = range
|
||||
|
||||
const result = await buildWhereClause(
|
||||
req.userId,
|
||||
group_id as string | undefined,
|
||||
['t.type = ?', 't.date >= ?', 't.date <= ?']
|
||||
)
|
||||
if (!result) return res.status(403).json({ code: 40300, message: '无权访问此群组' })
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`SELECT c.id, COALESCE(c.name, '未分类') as name, COALESCE(c.icon, '?') as icon, COALESCE(c.color, '#BFB3B3') as color, SUM(t.amount) as amount, COUNT(t.id) as count
|
||||
FROM transactions t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.user_id = ? AND t.type = ? AND t.date >= ? AND t.date <= ?
|
||||
${result.where}
|
||||
GROUP BY c.id
|
||||
ORDER BY amount DESC`,
|
||||
[req.userId, type, startDate, endDate]
|
||||
[...result.params, type, startDate, endDate]
|
||||
)
|
||||
|
||||
res.json({ code: 0, data: rows })
|
||||
@@ -82,7 +126,7 @@ router.get('/category', async (req: AuthRequest, res: Response) => {
|
||||
|
||||
router.get('/trend', async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { month, type = 'expense' } = req.query
|
||||
const { month, type = 'expense', group_id } = req.query
|
||||
if (!VALID_TYPES.includes(type as string)) {
|
||||
return res.status(400).json({ code: 40002, message: '类型无效' })
|
||||
}
|
||||
@@ -91,13 +135,20 @@ router.get('/trend', async (req: AuthRequest, res: Response) => {
|
||||
if (!range) return res.status(400).json({ code: 40001, message: '月份格式无效' })
|
||||
const { startDate, endDate } = range
|
||||
|
||||
const result = await buildWhereClause(
|
||||
req.userId,
|
||||
group_id as string | undefined,
|
||||
['t.type = ?', 't.date >= ?', 't.date <= ?']
|
||||
)
|
||||
if (!result) return res.status(403).json({ code: 40300, message: '无权访问此群组' })
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`SELECT DATE_FORMAT(date, '%Y-%m-%d') as date, SUM(amount) as amount
|
||||
FROM transactions
|
||||
WHERE user_id = ? AND type = ? AND date >= ? AND date <= ?
|
||||
GROUP BY date
|
||||
ORDER BY date`,
|
||||
[req.userId, type, startDate, endDate]
|
||||
`SELECT DATE_FORMAT(t.date, '%Y-%m-%d') as date, SUM(t.amount) as amount
|
||||
FROM transactions t
|
||||
${result.where}
|
||||
GROUP BY t.date
|
||||
ORDER BY t.date`,
|
||||
[...result.params, type, startDate, endDate]
|
||||
)
|
||||
|
||||
res.json({ code: 0, data: rows })
|
||||
|
||||
Reference in New Issue
Block a user