feat: enhance lottery system with participant import and prize config
- Fix ES module import issue in admin.service.ts (require -> import) - Fix lottery reveal ghosting by hiding name particles on complete - Add participant import from Excel with tag calculation - Add prize configuration service with JSON persistence - Constrain winners overlay to scroll area dimensions - Fix macOS lsof syntax in stop script - Add HorseRace view and renderer (WIP) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { Router, IRouter } from 'express';
|
||||
import multer from 'multer';
|
||||
import { participantService } from '../services/participant.service';
|
||||
import { prizeConfigService } from '../services/prize-config.service';
|
||||
|
||||
const router: IRouter = Router();
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
|
||||
/**
|
||||
* GET /api/admin/stats
|
||||
@@ -12,7 +16,7 @@ router.get('/stats', async (_req, res, next) => {
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
totalUsers: 0,
|
||||
totalUsers: participantService.getCount(),
|
||||
totalVotes: 0,
|
||||
activeConnections: 0,
|
||||
},
|
||||
@@ -22,6 +26,53 @@ router.get('/stats', async (_req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/prizes
|
||||
* Get prize configuration
|
||||
*/
|
||||
router.get('/prizes', async (_req, res, next) => {
|
||||
try {
|
||||
const config = prizeConfigService.getFullConfig();
|
||||
return res.json({
|
||||
success: true,
|
||||
data: config,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /api/admin/prizes
|
||||
* Update prize configuration
|
||||
*/
|
||||
router.put('/prizes', async (req, res, next) => {
|
||||
try {
|
||||
const { prizes, settings } = req.body;
|
||||
|
||||
if (prizes) {
|
||||
const result = await prizeConfigService.updatePrizes(prizes);
|
||||
if (!result.success) {
|
||||
return res.status(400).json({ success: false, error: result.error });
|
||||
}
|
||||
}
|
||||
|
||||
if (settings) {
|
||||
const result = await prizeConfigService.updateSettings(settings);
|
||||
if (!result.success) {
|
||||
return res.status(400).json({ success: false, error: result.error });
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: prizeConfigService.getFullConfig(),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/draw/start
|
||||
* Start a lucky draw
|
||||
@@ -54,4 +105,54 @@ router.post('/draw/stop', async (_req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/participants/import
|
||||
* Import participants from Excel file
|
||||
* Expected columns: 岗位, 姓名, 年份
|
||||
*/
|
||||
router.post('/participants/import', upload.single('file'), async (req, res, next) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '请上传 Excel 文件',
|
||||
});
|
||||
}
|
||||
|
||||
const result = await participantService.importFromExcel(req.file.buffer);
|
||||
|
||||
return res.json({
|
||||
success: result.success,
|
||||
data: {
|
||||
totalCount: result.totalCount,
|
||||
importedCount: result.importedCount,
|
||||
tagDistribution: result.tagDistribution,
|
||||
errors: result.errors,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/participants
|
||||
* Get all participants
|
||||
*/
|
||||
router.get('/participants', async (_req, res, next) => {
|
||||
try {
|
||||
const participants = participantService.getAll();
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
count: participants.length,
|
||||
participants,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user