quanyoumi/backend/routes/admin.js

503 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const auth = require('../middleware/auth');
const authSuper = require('../middleware/authSuper');
const { upload, getFileUrls } = require('../middleware/upload');
const { Admin, Activity, Registration, Expense, Photo, Sponsor, Prize } = require('../models');
const fs = require('fs');
const path = require('path');
// 管理员登录
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const admin = await Admin.findOne({ where: { username } });
if (!admin || !bcrypt.compareSync(password, admin.password)) {
return res.status(401).json({ error: '用户名或密码错误' });
}
const token = jwt.sign({ id: admin.id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({
token,
admin: {
id: admin.id,
username: admin.username,
name: admin.name,
role: admin.role
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ========== 管理员管理API仅超级管理员 ==========
// 获取管理员列表
router.get('/admins', authSuper, async (req, res) => {
try {
const admins = await Admin.findAll({
attributes: ['id', 'username', 'name', 'role', 'createdAt'],
order: [['createdAt', 'ASC']],
raw: true
});
res.json(admins);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 创建管理员
router.post('/admins', authSuper, async (req, res) => {
try {
const { username, password, name, role } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '用户名和密码不能为空' });
}
const existing = await Admin.findOne({ where: { username } });
if (existing) {
return res.status(400).json({ error: '用户名已存在' });
}
const hashedPassword = bcrypt.hashSync(password, 10);
const admin = await Admin.create({
username,
password: hashedPassword,
name: name || username,
role: role || 'admin'
});
res.json({
id: admin.id,
username: admin.username,
name: admin.name,
role: admin.role
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 修改管理员信息
router.put('/admins/:id', authSuper, async (req, res) => {
try {
const { name, role } = req.body;
const admin = await Admin.findByPk(req.params.id);
if (!admin) {
return res.status(404).json({ error: '管理员不存在' });
}
const updateData = {};
if (name !== undefined) updateData.name = name;
if (role !== undefined) updateData.role = role;
await Admin.update(updateData, { where: { id: req.params.id } });
const updated = await Admin.findByPk(req.params.id, {
attributes: ['id', 'username', 'name', 'role']
});
res.json(updated);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 修改管理员密码
router.put('/admins/:id/password', authSuper, async (req, res) => {
try {
const { newPassword } = req.body;
if (!newPassword) {
return res.status(400).json({ error: '新密码不能为空' });
}
const admin = await Admin.findByPk(req.params.id);
if (!admin) {
return res.status(404).json({ error: '管理员不存在' });
}
const hashedPassword = bcrypt.hashSync(newPassword, 10);
await Admin.update({ password: hashedPassword }, { where: { id: req.params.id } });
res.json({ message: '密码修改成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除管理员
router.delete('/admins/:id', authSuper, async (req, res) => {
try {
const admin = await Admin.findByPk(req.params.id);
if (!admin) {
return res.status(404).json({ error: '管理员不存在' });
}
// 防止删除超级管理员
if (admin.role === 'super') {
return res.status(403).json({ error: '不能删除超级管理员' });
}
await Admin.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 创建活动
router.post('/activities', auth, async (req, res) => {
try {
const activity = await Activity.create(req.body);
res.json(activity);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 编辑活动
router.put('/activities/:id', auth, async (req, res) => {
try {
await Activity.update(req.body, { where: { id: req.params.id } });
const activity = await Activity.findByPk(req.params.id);
res.json(activity);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 审核报名
router.put('/registrations/:id', auth, async (req, res) => {
try {
const { status, peopleCount } = req.body;
const updateData = {};
if (status) updateData.status = status;
if (peopleCount !== undefined) updateData.peopleCount = peopleCount;
await Registration.update(updateData, { where: { id: req.params.id } });
const registration = await Registration.findByPk(req.params.id);
res.json(registration);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 签到/取消签到
router.put('/registrations/:id/checkin', auth, async (req, res) => {
try {
const registration = await Registration.findByPk(req.params.id);
if (!registration) {
return res.status(404).json({ error: '报名信息不存在' });
}
const newStatus = !registration.checkInStatus;
await Registration.update({
checkInStatus: newStatus,
checkInTime: newStatus ? new Date() : null
}, { where: { id: req.params.id } });
const updated = await Registration.findByPk(req.params.id);
res.json(updated);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取所有报名(含待审核)
router.get('/registrations', auth, async (req, res) => {
try {
const { activityId } = req.query;
const where = activityId ? { activityId } : {};
const registrations = await Registration.findAll({ where, order: [['createdAt', 'DESC']] });
res.json(registrations);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除报名
router.delete('/registrations/:id', auth, async (req, res) => {
try {
await Registration.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 上传经费明细
router.post('/expenses', auth, upload.array('media', 5), async (req, res) => {
try {
const { activityId, item, amount, category, note } = req.body;
let receipt = null;
let media = [];
if (req.files && req.files.length > 0) {
const uploaded = getFileUrls(req.files);
media = uploaded.map(file => ({
url: file.url,
type: file.mimetype.startsWith('video/') ? 'video' : 'image'
}));
}
const expense = await Expense.create({
activityId,
item,
amount,
category,
receipt,
note,
media
});
res.json(expense);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取经费列表
router.get('/expenses', auth, async (req, res) => {
try {
const { activityId } = req.query;
const where = activityId ? { activityId } : {};
const expenses = await Expense.findAll({ where });
res.json(expenses);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 更新经费
router.put('/expenses/:id', auth, upload.array('media', 5), async (req, res) => {
try {
const { item, amount, category, note, existingMedia } = req.body;
const updateData = { item, amount, category, note };
// 处理现有媒体
let media = [];
if (existingMedia) {
try {
media = JSON.parse(existingMedia);
} catch (e) {
media = [];
}
}
// 处理新上传的媒体
if (req.files && req.files.length > 0) {
const uploaded = getFileUrls(req.files);
const newMedia = uploaded.map(file => ({
url: file.url,
type: file.mimetype.startsWith('video/') ? 'video' : 'image'
}));
media = [...media, ...newMedia].slice(0, 5); // 限制最多5个
}
updateData.media = media;
await Expense.update(updateData, { where: { id: req.params.id } });
const expense = await Expense.findByPk(req.params.id);
res.json(expense);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除经费
router.delete('/expenses/:id', auth, async (req, res) => {
try {
await Expense.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 上传活动照片
router.post('/photos', auth, upload.array('photos', 10), async (req, res) => {
try {
const { activityId } = req.body;
const uploaded = getFileUrls(req.files);
const photos = uploaded.map(file => ({
activityId,
url: file.url,
type: /\.(mp4|mov|avi)$/i.test(file.filename) ? 'video' : 'image',
status: 'approved'
}));
const created = await Photo.bulkCreate(photos);
res.json(created);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取待审核照片
router.get('/photos/pending', auth, async (req, res) => {
try {
const photos = await Photo.findAll({
where: { status: 'pending' },
order: [['createdAt', 'DESC']]
});
res.json(photos);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 审核照片
router.put('/photos/:id', auth, async (req, res) => {
try {
const { status } = req.body;
await Photo.update({ status }, { where: { id: req.params.id } });
const photo = await Photo.findByPk(req.params.id);
res.json(photo);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除照片
router.delete('/photos/:id', auth, async (req, res) => {
try {
const photo = await Photo.findByPk(req.params.id);
if (!photo) {
return res.status(404).json({ error: '照片不存在' });
}
// 删除本地文件
const filename = photo.url.split('/').pop();
const filePath = path.join(__dirname, '../uploads', filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
// 从数据库删除记录
await photo.destroy();
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 添加赞助商
router.post('/sponsors', auth, upload.single('logo'), async (req, res) => {
try {
const { activityId, name, type, link, sortOrder } = req.body;
let logo = null;
if (req.file) {
const [uploaded] = getFileUrls(req.file);
logo = uploaded.url;
}
const sponsor = await Sponsor.create({
activityId,
name,
logo,
type,
link,
sortOrder: sortOrder || 0
});
res.json(sponsor);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 更新赞助商
router.put('/sponsors/:id', auth, upload.single('logo'), async (req, res) => {
try {
const { name, type, link, sortOrder, removeLogo } = req.body;
const updateData = { name, type, link };
if (sortOrder !== undefined) {
updateData.sortOrder = sortOrder;
}
if (req.file) {
const [uploaded] = getFileUrls(req.file);
updateData.logo = uploaded.url;
} else if (removeLogo === 'true') {
updateData.logo = null;
}
await Sponsor.update(updateData, { where: { id: req.params.id } });
const sponsor = await Sponsor.findByPk(req.params.id);
res.json(sponsor);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除赞助商
router.delete('/sponsors/:id', auth, async (req, res) => {
try {
await Sponsor.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 添加奖品
router.post('/prizes', auth, upload.single('image'), async (req, res) => {
try {
const { activityId, name, level, description, quantity, sortOrder, source, sponsor } = req.body;
let image = null;
if (req.file) {
const [uploaded] = getFileUrls([req.file]);
image = uploaded.url;
}
const prize = await Prize.create({
activityId,
name,
level,
description,
image,
quantity: quantity || 1,
sortOrder: sortOrder || 0,
source: source || 'purchase',
sponsor: sponsor || null
});
res.json(prize);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 更新奖品
router.put('/prizes/:id', auth, upload.single('image'), async (req, res) => {
try {
const { name, level, description, quantity, sortOrder, source, sponsor, removeImage } = req.body;
const updateData = { name, level, description, quantity, sortOrder, source, sponsor };
if (req.file) {
const [uploaded] = getFileUrls([req.file]);
updateData.image = uploaded.url;
} else if (removeImage === 'true') {
updateData.image = null;
}
await Prize.update(updateData, { where: { id: req.params.id } });
const prize = await Prize.findByPk(req.params.id);
res.json(prize);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 删除奖品
router.delete('/prizes/:id', auth, async (req, res) => {
try {
await Prize.destroy({ where: { id: req.params.id } });
res.json({ message: '删除成功' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;