503 lines
14 KiB
JavaScript
503 lines
14 KiB
JavaScript
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;
|