feat: 添加管理员管理和场地图片功能
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
const { Expense } = require('./models');
|
||||
|
||||
async function fixExpenseMedia() {
|
||||
try {
|
||||
console.log('开始修复经费媒体数据...');
|
||||
|
||||
const expenses = await Expense.findAll();
|
||||
console.log(`找到 ${expenses.length} 条经费记录`);
|
||||
|
||||
let fixedCount = 0;
|
||||
|
||||
for (const expense of expenses) {
|
||||
let needUpdate = false;
|
||||
let media = expense.media || [];
|
||||
|
||||
// 检查是否有异常数据
|
||||
if (Array.isArray(media) && media.length > 5) {
|
||||
console.log(`经费 #${expense.id} 有 ${media.length} 个媒体文件,超过限制`);
|
||||
media = media.slice(0, 5);
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
// 检查是否有无效的媒体项
|
||||
const validMedia = media.filter(item => {
|
||||
return item && item.url && (item.type === 'image' || item.type === 'video');
|
||||
});
|
||||
|
||||
if (validMedia.length !== media.length) {
|
||||
console.log(`经费 #${expense.id} 有无效媒体项,从 ${media.length} 清理到 ${validMedia.length}`);
|
||||
media = validMedia;
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
if (needUpdate) {
|
||||
await expense.update({ media });
|
||||
fixedCount++;
|
||||
console.log(`✓ 修复经费 #${expense.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n修复完成!共修复 ${fixedCount} 条记录`);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('修复失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fixExpenseMedia();
|
||||
+50
-23
@@ -5,39 +5,66 @@ const { Admin, sequelize } = require('./models');
|
||||
|
||||
async function initAdmin() {
|
||||
try {
|
||||
// 使用 force: false 避免 alter 表时的问题
|
||||
// 如果表不存在则创建,存在则跳过
|
||||
await sequelize.sync({ force: false });
|
||||
|
||||
// 检查是否已存在管理员账号
|
||||
const existingAdmin = await Admin.findOne({ where: { username: 'admin' } });
|
||||
// 检查role字段是否存在
|
||||
const [results] = await sequelize.query("PRAGMA table_info(Admins);");
|
||||
const hasRoleColumn = results.some(col => col.name === 'role');
|
||||
|
||||
if (existingAdmin) {
|
||||
console.log('✓ 管理员账号已存在');
|
||||
console.log(' 用户名: admin');
|
||||
console.log(' 提示: 如需重置密码,请删除数据库文件后重新初始化');
|
||||
process.exit(0);
|
||||
if (!hasRoleColumn) {
|
||||
console.log('⚠️ role字段不存在,请先运行迁移: node backend/migrations/index.js');
|
||||
console.log(' 或运行: npm run migrate (在backend目录)');
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedPassword = bcrypt.hashSync('admin123', 10);
|
||||
await Admin.create({
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
name: '管理员'
|
||||
});
|
||||
// 检查yvan超级管理员
|
||||
const yvan = await Admin.findOne({ where: { username: 'yvan' } });
|
||||
if (!yvan) {
|
||||
const hashedPassword = bcrypt.hashSync('Xu950329.', 10);
|
||||
await Admin.create({
|
||||
username: 'yvan',
|
||||
password: hashedPassword,
|
||||
name: 'Yvan',
|
||||
role: 'super'
|
||||
});
|
||||
console.log('✓ 超级管理员yvan创建成功');
|
||||
} else {
|
||||
// 确保yvan是super角色
|
||||
if (yvan.role !== 'super') {
|
||||
await yvan.update({ role: 'super' });
|
||||
console.log('✓ 超级管理员yvan角色已更新');
|
||||
} else {
|
||||
console.log('✓ 超级管理员yvan已存在');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✓ 管理员账号创建成功');
|
||||
console.log(' 用户名: admin');
|
||||
console.log(' 密码: admin123');
|
||||
// 检查默认管理员
|
||||
const admin = await Admin.findOne({ where: { username: 'admin' } });
|
||||
if (!admin) {
|
||||
const hashedPassword = bcrypt.hashSync('admin123', 10);
|
||||
await Admin.create({
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
name: '管理员',
|
||||
role: 'admin'
|
||||
});
|
||||
console.log('✓ 默认管理员admin创建成功');
|
||||
} else {
|
||||
// 确保admin是admin角色
|
||||
if (!admin.role || admin.role === 'super') {
|
||||
await admin.update({ role: 'admin' });
|
||||
console.log('✓ 默认管理员admin角色已更新');
|
||||
} else {
|
||||
console.log('✓ 默认管理员admin已存在');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n初始化完成!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('✗ 初始化失败:', error.message);
|
||||
if (error.name === 'SequelizeUniqueConstraintError') {
|
||||
console.error(' 原因: 管理员账号已存在');
|
||||
} else {
|
||||
console.error(' 详细错误:', error);
|
||||
}
|
||||
console.error(' 详细错误:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { Admin } = require('../models');
|
||||
|
||||
const authSuper = async (req, res, next) => {
|
||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: '未授权访问' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const admin = await Admin.findByPk(decoded.id);
|
||||
|
||||
if (!admin || admin.role !== 'super') {
|
||||
return res.status(403).json({ error: '需要超级管理员权限' });
|
||||
}
|
||||
|
||||
req.adminId = decoded.id;
|
||||
req.admin = admin;
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: '无效的token' });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = authSuper;
|
||||
@@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
async up(manager) {
|
||||
console.log(' - 添加奖品来源字段...');
|
||||
|
||||
// 添加source字段,默认值为purchase
|
||||
await manager.executeSql(`
|
||||
ALTER TABLE Prizes ADD COLUMN source TEXT DEFAULT 'purchase';
|
||||
`);
|
||||
|
||||
// 添加sponsor字段
|
||||
await manager.executeSql(`
|
||||
ALTER TABLE Prizes ADD COLUMN sponsor TEXT;
|
||||
`);
|
||||
|
||||
// 为现有数据设置默认值
|
||||
await manager.executeSql(`
|
||||
UPDATE Prizes SET source = 'purchase' WHERE source IS NULL;
|
||||
`);
|
||||
|
||||
console.log(' - 奖品来源字段添加完成');
|
||||
},
|
||||
|
||||
async down(manager) {
|
||||
console.log(' - 移除奖品来源字段...');
|
||||
|
||||
// SQLite不支持DROP COLUMN,需要重建表
|
||||
await manager.executeSql(`
|
||||
CREATE TABLE Prizes_backup AS SELECT
|
||||
id, activityId, name, level, description, image, quantity, sortOrder, createdAt, updatedAt
|
||||
FROM Prizes;
|
||||
`);
|
||||
|
||||
await manager.executeSql(`DROP TABLE Prizes;`);
|
||||
|
||||
await manager.executeSql(`ALTER TABLE Prizes_backup RENAME TO Prizes;`);
|
||||
|
||||
console.log(' - 奖品来源字段移除完成');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
module.exports = {
|
||||
async up(manager) {
|
||||
console.log(' - 添加特等奖等级...');
|
||||
|
||||
// SQLite不支持直接修改CHECK约束,需要重建表
|
||||
// 1. 创建新表结构(包含特等奖)
|
||||
await manager.executeSql(`
|
||||
CREATE TABLE Prizes_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
activityId INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
|
||||
description TEXT,
|
||||
image TEXT,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
sortOrder INTEGER DEFAULT 0,
|
||||
source TEXT DEFAULT 'purchase',
|
||||
sponsor TEXT,
|
||||
createdAt DATETIME NOT NULL,
|
||||
updatedAt DATETIME NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. 复制数据
|
||||
await manager.executeSql(`
|
||||
INSERT INTO Prizes_new
|
||||
SELECT * FROM Prizes;
|
||||
`);
|
||||
|
||||
// 3. 删除旧表
|
||||
await manager.executeSql(`DROP TABLE Prizes;`);
|
||||
|
||||
// 4. 重命名新表
|
||||
await manager.executeSql(`ALTER TABLE Prizes_new RENAME TO Prizes;`);
|
||||
|
||||
console.log(' - 特等奖等级添加完成');
|
||||
},
|
||||
|
||||
async down(manager) {
|
||||
console.log(' - 移除特等奖等级...');
|
||||
|
||||
// 1. 创建旧表结构(不包含特等奖)
|
||||
await manager.executeSql(`
|
||||
CREATE TABLE Prizes_old (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
activityId INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
level TEXT NOT NULL CHECK(level IN ('一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
|
||||
description TEXT,
|
||||
image TEXT,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
sortOrder INTEGER DEFAULT 0,
|
||||
source TEXT DEFAULT 'purchase',
|
||||
sponsor TEXT,
|
||||
createdAt DATETIME NOT NULL,
|
||||
updatedAt DATETIME NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. 复制数据(排除特等奖)
|
||||
await manager.executeSql(`
|
||||
INSERT INTO Prizes_old
|
||||
SELECT * FROM Prizes WHERE level != '特等奖';
|
||||
`);
|
||||
|
||||
// 3. 删除新表
|
||||
await manager.executeSql(`DROP TABLE Prizes;`);
|
||||
|
||||
// 4. 重命名旧表
|
||||
await manager.executeSql(`ALTER TABLE Prizes_old RENAME TO Prizes;`);
|
||||
|
||||
console.log(' - 特等奖等级移除完成');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// 添加管理员角色字段
|
||||
module.exports = {
|
||||
async up(manager) {
|
||||
console.log(' - 添加 Admins.role 字段...');
|
||||
await manager.executeSql('ALTER TABLE Admins ADD COLUMN role TEXT DEFAULT "admin";');
|
||||
console.log(' - 角色字段添加完成');
|
||||
},
|
||||
|
||||
async down(manager) {
|
||||
await manager.executeSql('ALTER TABLE Admins DROP COLUMN role;');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// 添加四等奖等级
|
||||
module.exports = {
|
||||
async up(manager) {
|
||||
console.log(' - 添加四等奖等级...');
|
||||
|
||||
// SQLite不支持直接修改CHECK约束,需要重建表
|
||||
// 1. 创建新表结构(包含四等奖)
|
||||
await manager.executeSql(`
|
||||
CREATE TABLE Prizes_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
activityId INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '四等奖', '参与奖', '伴手礼')),
|
||||
description TEXT,
|
||||
image TEXT,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
sortOrder INTEGER DEFAULT 0,
|
||||
source TEXT DEFAULT 'purchase',
|
||||
sponsor TEXT,
|
||||
createdAt DATETIME NOT NULL,
|
||||
updatedAt DATETIME NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. 复制数据
|
||||
await manager.executeSql(`
|
||||
INSERT INTO Prizes_new
|
||||
SELECT * FROM Prizes;
|
||||
`);
|
||||
|
||||
// 3. 删除旧表
|
||||
await manager.executeSql(`DROP TABLE Prizes;`);
|
||||
|
||||
// 4. 重命名新表
|
||||
await manager.executeSql(`ALTER TABLE Prizes_new RENAME TO Prizes;`);
|
||||
|
||||
console.log(' - 四等奖等级添加完成');
|
||||
},
|
||||
|
||||
async down(manager) {
|
||||
console.log(' - 移除四等奖等级...');
|
||||
|
||||
// 1. 创建旧表结构(不包含四等奖)
|
||||
await manager.executeSql(`
|
||||
CREATE TABLE Prizes_old (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
activityId INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
level TEXT NOT NULL CHECK(level IN ('特等奖', '一等奖', '二等奖', '三等奖', '参与奖', '伴手礼')),
|
||||
description TEXT,
|
||||
image TEXT,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
sortOrder INTEGER DEFAULT 0,
|
||||
source TEXT DEFAULT 'purchase',
|
||||
sponsor TEXT,
|
||||
createdAt DATETIME NOT NULL,
|
||||
updatedAt DATETIME NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// 2. 复制数据(排除四等奖)
|
||||
await manager.executeSql(`
|
||||
INSERT INTO Prizes_old
|
||||
SELECT * FROM Prizes WHERE level != '四等奖';
|
||||
`);
|
||||
|
||||
// 3. 删除新表
|
||||
await manager.executeSql(`DROP TABLE Prizes;`);
|
||||
|
||||
// 4. 重命名旧表
|
||||
await manager.executeSql(`ALTER TABLE Prizes_old RENAME TO Prizes;`);
|
||||
|
||||
console.log(' - 四等奖等级移除完成');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 迁移 0009: 添加进群状态字段
|
||||
* 为 Registrations 表添加 groupStatus 字段
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
async up(manager) {
|
||||
console.log(' - 添加 Registrations.groupStatus 字段...');
|
||||
await manager.executeSql(`
|
||||
ALTER TABLE Registrations ADD COLUMN groupStatus INTEGER DEFAULT 0;
|
||||
`);
|
||||
},
|
||||
|
||||
async down(manager) {
|
||||
console.log(' - 移除 Registrations.groupStatus 字段...');
|
||||
await manager.executeSql(`
|
||||
ALTER TABLE Registrations DROP COLUMN groupStatus;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -16,7 +16,12 @@ const Admin = sequelize.define('Admin', {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
name: DataTypes.STRING
|
||||
name: DataTypes.STRING,
|
||||
role: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'admin'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Admin;
|
||||
|
||||
+10
-1
@@ -16,7 +16,7 @@ const Prize = sequelize.define('Prize', {
|
||||
allowNull: false
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.ENUM('一等奖', '二等奖', '三等奖', '参与奖', '伴手礼'),
|
||||
type: DataTypes.ENUM('特等奖', '一等奖', '二等奖', '三等奖', '四等奖', '参与奖', '伴手礼'),
|
||||
allowNull: false
|
||||
},
|
||||
description: DataTypes.TEXT,
|
||||
@@ -29,6 +29,15 @@ const Prize = sequelize.define('Prize', {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '排序值,数字越小越靠前'
|
||||
},
|
||||
source: {
|
||||
type: DataTypes.ENUM('member', 'sponsor', 'purchase'),
|
||||
defaultValue: 'purchase',
|
||||
comment: '来源: member-群友赞助, sponsor-赞助商提供, purchase-采购'
|
||||
},
|
||||
sponsor: {
|
||||
type: DataTypes.STRING,
|
||||
comment: '赞助者名称(群友或赞助商)'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,11 @@ const Registration = sequelize.define('Registration', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
checkInTime: DataTypes.DATE
|
||||
checkInTime: DataTypes.DATE,
|
||||
groupStatus: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Registration;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('./db');
|
||||
|
||||
const VenueImage = sequelize.define('VenueImage', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
activityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
image: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
sortOrder: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
comment: '排序值,数字越小越靠前'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = VenueImage;
|
||||
@@ -6,6 +6,7 @@ const Photo = require('./Photo');
|
||||
const Sponsor = require('./Sponsor');
|
||||
const Prize = require('./Prize');
|
||||
const Admin = require('./Admin');
|
||||
const VenueImage = require('./VenueImage');
|
||||
|
||||
// 同步数据库
|
||||
// 使用 force: false 避免 SQLite alter 表时的约束冲突问题
|
||||
@@ -23,5 +24,6 @@ module.exports = {
|
||||
Photo,
|
||||
Sponsor,
|
||||
Prize,
|
||||
Admin
|
||||
Admin,
|
||||
VenueImage
|
||||
};
|
||||
|
||||
Generated
+1102
-1
File diff suppressed because it is too large
Load Diff
+56
-10
@@ -1,6 +1,6 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Activity, Photo, Sponsor, Expense, Registration, Prize } = require('../models');
|
||||
const { Activity, Photo, Sponsor, Expense, Registration, Prize, VenueImage } = require('../models');
|
||||
const { upload, getFileUrls } = require('../middleware/upload');
|
||||
|
||||
// 获取活动列表
|
||||
@@ -8,7 +8,11 @@ router.get('/activities', async (req, res) => {
|
||||
try {
|
||||
const { status } = req.query;
|
||||
const where = status ? { status } : {};
|
||||
const activities = await Activity.findAll({ where, order: [['startTime', 'DESC']] });
|
||||
const activities = await Activity.findAll({
|
||||
where,
|
||||
order: [['startTime', 'DESC']],
|
||||
raw: true
|
||||
});
|
||||
res.json(activities);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -18,22 +22,42 @@ router.get('/activities', async (req, res) => {
|
||||
// 获取活动详情
|
||||
router.get('/activities/:id', async (req, res) => {
|
||||
try {
|
||||
const activity = await Activity.findByPk(req.params.id);
|
||||
const activity = await Activity.findByPk(req.params.id, { raw: true });
|
||||
if (!activity) {
|
||||
return res.status(404).json({ error: '活动不存在' });
|
||||
}
|
||||
|
||||
// 只返回已审核通过的照片
|
||||
const photos = await Photo.findAll({
|
||||
where: { activityId: req.params.id, status: 'approved' }
|
||||
where: { activityId: req.params.id, status: 'approved' },
|
||||
raw: true
|
||||
});
|
||||
const sponsors = await Sponsor.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']]
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
|
||||
const expenses = await Expense.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
raw: true
|
||||
});
|
||||
// 手动解析JSON字段
|
||||
const parsedExpenses = expenses.map(expense => ({
|
||||
...expense,
|
||||
media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
|
||||
}));
|
||||
const prizes = await Prize.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']]
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
const venueImages = await VenueImage.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
|
||||
res.json({ activity, photos, sponsors, expenses, prizes });
|
||||
res.json({ activity, photos, sponsors, expenses: parsedExpenses, prizes, venueImages: venueImages.map(img => img.image).filter(Boolean) });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
@@ -42,8 +66,30 @@ router.get('/activities/:id', async (req, res) => {
|
||||
// 获取活动经费(公开接口)
|
||||
router.get('/activities/:id/expenses', async (req, res) => {
|
||||
try {
|
||||
const expenses = await Expense.findAll({ where: { activityId: req.params.id } });
|
||||
res.json(expenses);
|
||||
const expenses = await Expense.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
raw: true
|
||||
});
|
||||
// 手动解析JSON字段
|
||||
const parsedExpenses = expenses.map(expense => ({
|
||||
...expense,
|
||||
media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
|
||||
}));
|
||||
res.json(parsedExpenses);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取场地图片列表
|
||||
router.get('/activities/:id/venue-images', async (req, res) => {
|
||||
try {
|
||||
const images = await VenueImage.findAll({
|
||||
where: { activityId: req.params.id },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
res.json(images.map(img => img.image).filter(Boolean));
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
+44
-3
@@ -32,6 +32,20 @@ router.post('/login', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Token续期
|
||||
router.post('/refresh-token', auth, async (req, res) => {
|
||||
try {
|
||||
const admin = await Admin.findByPk(req.adminId);
|
||||
if (!admin) {
|
||||
return res.status(401).json({ error: '用户不存在' });
|
||||
}
|
||||
const token = jwt.sign({ id: admin.id }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
||||
res.json({ token });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 管理员管理API(仅超级管理员) ==========
|
||||
|
||||
// 获取管理员列表
|
||||
@@ -173,10 +187,12 @@ router.put('/activities/:id', auth, async (req, res) => {
|
||||
// 审核报名
|
||||
router.put('/registrations/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const { status, peopleCount } = req.body;
|
||||
const { status, peopleCount, carNumber, groupStatus } = req.body;
|
||||
const updateData = {};
|
||||
if (status) updateData.status = status;
|
||||
if (peopleCount !== undefined) updateData.peopleCount = peopleCount;
|
||||
if (carNumber !== undefined) updateData.carNumber = carNumber;
|
||||
if (groupStatus !== undefined) updateData.groupStatus = groupStatus;
|
||||
|
||||
await Registration.update(updateData, { where: { id: req.params.id } });
|
||||
const registration = await Registration.findByPk(req.params.id);
|
||||
@@ -207,6 +223,26 @@ router.put('/registrations/:id/checkin', auth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 进群/取消进群
|
||||
router.put('/registrations/:id/group', auth, async (req, res) => {
|
||||
try {
|
||||
const registration = await Registration.findByPk(req.params.id);
|
||||
if (!registration) {
|
||||
return res.status(404).json({ error: '报名信息不存在' });
|
||||
}
|
||||
|
||||
const newStatus = !registration.groupStatus;
|
||||
await Registration.update({
|
||||
groupStatus: newStatus
|
||||
}, { 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 {
|
||||
@@ -264,8 +300,13 @@ 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);
|
||||
const expenses = await Expense.findAll({ where, raw: true });
|
||||
// 手动解析JSON字段
|
||||
const parsedExpenses = expenses.map(expense => ({
|
||||
...expense,
|
||||
media: typeof expense.media === 'string' ? JSON.parse(expense.media || '[]') : (expense.media || [])
|
||||
}));
|
||||
res.json(parsedExpenses);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ router.post('/registrations', async (req, res) => {
|
||||
activityId, name, phone, carModel, carNumber, note
|
||||
});
|
||||
|
||||
res.json(registration);
|
||||
res.json(registration.toJSON());
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
@@ -25,7 +25,7 @@ router.post('/registrations', async (req, res) => {
|
||||
// 查询报名状态
|
||||
router.get('/registrations/:id', async (req, res) => {
|
||||
try {
|
||||
const registration = await Registration.findByPk(req.params.id);
|
||||
const registration = await Registration.findByPk(req.params.id, { raw: true });
|
||||
res.json(registration);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -36,7 +36,8 @@ router.get('/registrations/:id', async (req, res) => {
|
||||
router.get('/activities/:id/registrations', async (req, res) => {
|
||||
try {
|
||||
const registrations = await Registration.findAll({
|
||||
where: { activityId: req.params.id, status: 'approved' }
|
||||
where: { activityId: req.params.id, status: 'approved' },
|
||||
raw: true
|
||||
});
|
||||
res.json(registrations);
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const auth = require('../middleware/auth');
|
||||
const { upload, getFileUrls } = require('../middleware/upload');
|
||||
const { VenueImage } = require('../models');
|
||||
|
||||
// 获取场地图片列表
|
||||
router.get('/venue-images', auth, async (req, res) => {
|
||||
try {
|
||||
const { activityId } = req.query;
|
||||
const images = await VenueImage.findAll({
|
||||
where: { activityId },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
raw: true
|
||||
});
|
||||
res.json(images);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 上传场地图片
|
||||
router.post('/venue-images', auth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
const { activityId, sortOrder } = req.body;
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: '请选择图片' });
|
||||
}
|
||||
|
||||
const [uploaded] = getFileUrls([req.file]);
|
||||
const image = await VenueImage.create({
|
||||
activityId,
|
||||
image: uploaded.url,
|
||||
sortOrder: sortOrder || 0
|
||||
});
|
||||
|
||||
res.json(image);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新场地图片排序
|
||||
router.put('/venue-images/:id', auth, async (req, res) => {
|
||||
try {
|
||||
const { sortOrder } = req.body;
|
||||
await VenueImage.update({ sortOrder }, { where: { id: req.params.id } });
|
||||
const image = await VenueImage.findByPk(req.params.id);
|
||||
res.json(image);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除场地图片
|
||||
router.delete('/venue-images/:id', auth, async (req, res) => {
|
||||
try {
|
||||
await VenueImage.destroy({ where: { id: req.params.id } });
|
||||
res.json({ message: '删除成功' });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -16,6 +16,7 @@ app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
app.use('/api', require('./routes/activity'));
|
||||
app.use('/api', require('./routes/registration'));
|
||||
app.use('/api/admin', require('./routes/admin'));
|
||||
app.use('/api/admin', require('./routes/venue'));
|
||||
|
||||
// 生产环境下提供前端静态文件
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
||||
Reference in New Issue
Block a user