feat: 初始化义工时长管理系统

- 管理台:活动设置、时长登记、用户管理、数据导出(Excel/PNG)
- 用户端:姓名查询个人义工时长与活动记录
- 技术栈:React + Vite + TailwindCSS / Node.js + Express + sql.js (SQLite)
- 支持 Docker 部署,附带 Gitea Actions 自动部署工作流
- 响应式界面,兼容移动端与 PC 端
- 默认管理员账号:admin / admin123
This commit is contained in:
Trae Assistant
2026-10-04 18:06:24 +08:00
commit 25b2b8f381
50 changed files with 7918 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
import { Router } from 'express';
import { getDb } from '../db';
import { authMiddleware, AuthRequest } from '../middleware/auth';
const router = Router();
router.post('/', authMiddleware, (req: AuthRequest, res) => {
const { include_users, include_activities, include_hours, include_user_totals, include_user_activities } = req.body || {};
const db = getDb();
const result: any = {};
if (include_users) {
result.users = db.prepareCompat(`
SELECT u.id, u.class, u.name, u.volunteer_no, g.name AS group_name
FROM users u LEFT JOIN work_groups g ON u.group_id = g.id
ORDER BY u.class, u.name
`).all();
}
if (include_activities) {
result.activities = db.prepareCompat(`
SELECT a.*, GROUP_CONCAT(g.name) AS groups_name
FROM activities a
LEFT JOIN activity_groups ag ON a.id = ag.activity_id
LEFT JOIN work_groups g ON ag.group_id = g.id
GROUP BY a.id
ORDER BY a.start_time DESC
`).all();
}
if (include_hours) {
result.hours = db.prepareCompat(`
SELECT h.hours, h.remark, h.created_at,
a.title AS activity_title, a.start_time, a.end_time,
u.class, u.name, u.volunteer_no, g.name AS group_name
FROM hours h
JOIN activities a ON h.activity_id = a.id
JOIN users u ON h.user_id = u.id
LEFT JOIN work_groups g ON u.group_id = g.id
ORDER BY a.start_time DESC, u.class, u.name
`).all();
}
if (include_user_totals) {
result.user_totals = db.prepareCompat(`
SELECT u.id, u.class, u.name, u.volunteer_no, g.name AS group_name,
COALESCE(SUM(h.hours), 0) AS total_hours,
COUNT(DISTINCT h.activity_id) AS activity_count
FROM users u
LEFT JOIN work_groups g ON u.group_id = g.id
LEFT JOIN hours h ON h.user_id = u.id
GROUP BY u.id
ORDER BY total_hours DESC, u.class, u.name
`).all();
}
if (include_user_activities) {
const users = db.prepareCompat(`
SELECT u.id, u.class, u.name, u.volunteer_no, g.name AS group_name
FROM users u LEFT JOIN work_groups g ON u.group_id = g.id
ORDER BY u.class, u.name
`).all() as any[];
result.user_activities = users.map((u) => {
const acts = db.prepareCompat(`
SELECT a.title, a.start_time, a.end_time, h.hours, h.remark
FROM hours h JOIN activities a ON h.activity_id = a.id
WHERE h.user_id = ? ORDER BY a.start_time DESC
`).all(u.id);
return { ...u, activities: acts };
});
}
res.json(result);
});
export default router;