- 数据库:新增 user_groups 多对多关联表,启动时自动迁移旧 group_id 数据 - 后端 users:所有查询聚合多工作组信息,增删改接口支持 group_ids 数组;新增 POST /users/batch 批量添加接口(事务+逐行错误反馈) - 后端 export/hours/activities:全面兼容多工作组关联查询 - 前端 Users:新增/编辑改为工作组多选复选框;新增批量添加模态框(粘贴解析+实时校验表+提交汇总) - 前端列表/详情:工作组以多标签形式展示,搜索支持工作组名称
This commit is contained in:
+59
-16
@@ -4,17 +4,34 @@ import { authMiddleware, AuthRequest } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
function withGroupNames(rows: any[]): any[] {
|
||||
return rows.map((r) => {
|
||||
const names: string[] = [];
|
||||
if (r._group_names_csv) {
|
||||
for (const s of String(r._group_names_csv).split(',')) {
|
||||
if (s && s.trim()) names.push(s.trim());
|
||||
}
|
||||
}
|
||||
return { ...r, group_name: names.join('、') || null };
|
||||
});
|
||||
}
|
||||
|
||||
router.post('/', authMiddleware, (req: AuthRequest, res) => {
|
||||
const { include_users, include_activities, include_hours, include_user_totals, include_user_activities, include_activity_matrix } = 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
|
||||
const rows = db.prepareCompat(`
|
||||
SELECT u.id, u.class, u.name, u.volunteer_no,
|
||||
GROUP_CONCAT(g.name) AS _group_names_csv
|
||||
FROM users u
|
||||
LEFT JOIN user_groups ug ON u.id = ug.user_id
|
||||
LEFT JOIN work_groups g ON ug.group_id = g.id
|
||||
GROUP BY u.id
|
||||
ORDER BY u.class, u.name
|
||||
`).all();
|
||||
result.users = withGroupNames(rows).map(({ _group_names_csv: _, ...rest }) => rest);
|
||||
}
|
||||
if (include_activities) {
|
||||
result.activities = db.prepareCompat(`
|
||||
@@ -27,35 +44,49 @@ router.post('/', authMiddleware, (req: AuthRequest, res) => {
|
||||
`).all();
|
||||
}
|
||||
if (include_hours) {
|
||||
result.hours = db.prepareCompat(`
|
||||
const rows = 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
|
||||
u.class, u.name, u.volunteer_no,
|
||||
(SELECT GROUP_CONCAT(g2.name)
|
||||
FROM user_groups ug2
|
||||
LEFT JOIN work_groups g2 ON ug2.group_id = g2.id
|
||||
WHERE ug2.user_id = u.id) AS _group_names_csv
|
||||
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();
|
||||
result.hours = withGroupNames(rows).map(({ _group_names_csv: _, ...rest }) => rest);
|
||||
}
|
||||
if (include_user_totals) {
|
||||
result.user_totals = db.prepareCompat(`
|
||||
SELECT u.id, u.class, u.name, u.volunteer_no, g.name AS group_name,
|
||||
const rows = db.prepareCompat(`
|
||||
SELECT u.id, u.class, u.name, u.volunteer_no,
|
||||
(SELECT GROUP_CONCAT(g2.name)
|
||||
FROM user_groups ug2
|
||||
LEFT JOIN work_groups g2 ON ug2.group_id = g2.id
|
||||
WHERE ug2.user_id = u.id) AS _group_names_csv,
|
||||
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();
|
||||
result.user_totals = withGroupNames(rows).map(({ _group_names_csv: _, ...rest }) => rest);
|
||||
}
|
||||
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
|
||||
const userRows = db.prepareCompat(`
|
||||
SELECT u.id, u.class, u.name, u.volunteer_no,
|
||||
(SELECT GROUP_CONCAT(g2.name)
|
||||
FROM user_groups ug2
|
||||
LEFT JOIN work_groups g2 ON ug2.group_id = g2.id
|
||||
WHERE ug2.user_id = u.id) AS _group_names_csv
|
||||
FROM users u
|
||||
GROUP BY u.id
|
||||
ORDER BY u.class, u.name
|
||||
`).all() as any[];
|
||||
const users = withGroupNames(userRows).map(({ _group_names_csv: _, ...rest }) => rest);
|
||||
result.user_activities = users.map((u) => {
|
||||
const acts = db.prepareCompat(`
|
||||
SELECT a.title, a.start_time, a.end_time, h.hours, h.remark
|
||||
@@ -69,11 +100,17 @@ router.post('/', authMiddleware, (req: AuthRequest, res) => {
|
||||
const activities = db.prepareCompat(`
|
||||
SELECT id, title, start_time FROM activities ORDER BY start_time DESC, id DESC
|
||||
`).all() as any[];
|
||||
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
|
||||
const userRows = db.prepareCompat(`
|
||||
SELECT u.id, u.class, u.name, u.volunteer_no,
|
||||
(SELECT GROUP_CONCAT(g2.name)
|
||||
FROM user_groups ug2
|
||||
LEFT JOIN work_groups g2 ON ug2.group_id = g2.id
|
||||
WHERE ug2.user_id = u.id) AS _group_names_csv
|
||||
FROM users u
|
||||
GROUP BY u.id
|
||||
ORDER BY u.class, u.name
|
||||
`).all() as any[];
|
||||
const users = withGroupNames(userRows);
|
||||
const hoursRows = db.prepareCompat(`
|
||||
SELECT user_id, activity_id, hours FROM hours
|
||||
`).all() as any[];
|
||||
@@ -85,7 +122,13 @@ router.post('/', authMiddleware, (req: AuthRequest, res) => {
|
||||
result.activity_matrix = {
|
||||
activities,
|
||||
users: users.map((u) => {
|
||||
const row: Record<string, any> = { id: u.id, class: u.class, name: u.name, volunteer_no: u.volunteer_no, group_name: u.group_name };
|
||||
const row: Record<string, any> = {
|
||||
id: u.id,
|
||||
class: u.class,
|
||||
name: u.name,
|
||||
volunteer_no: u.volunteer_no,
|
||||
group_name: u.group_name,
|
||||
};
|
||||
for (const a of activities) {
|
||||
row[String(a.id)] = Number((matrix[`${u.id}|${a.id}`] || 0).toFixed(2));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user