diff --git a/packages/client-screen/src/views/AdminControl.vue b/packages/client-screen/src/views/AdminControl.vue index bdde559..e8f7611 100644 --- a/packages/client-screen/src/views/AdminControl.vue +++ b/packages/client-screen/src/views/AdminControl.vue @@ -128,6 +128,25 @@ const tagLabels: Record = { 'horse': '属马', }; +// Load existing participants from server +async function loadParticipants() { + try { + const response = await fetch('/api/admin/participants'); + const data = await response.json(); + if (data.success && data.data?.count > 0) { + importResult.value = { + success: true, + totalCount: data.data.count, + importedCount: data.data.count, + tagDistribution: data.data.tagDistribution || {}, + errors: [], + }; + } + } catch (error) { + console.error('Failed to load participants:', error); + } +} + // Navigation function goBack() { router.push('/'); @@ -348,6 +367,8 @@ onMounted(() => { admin.connect(); // 加载服务器奖项配置 loadPrizeConfig(); + // 加载已导入的参与者数据 + loadParticipants(); }); @@ -1687,13 +1708,13 @@ $admin-danger: #ef4444; .result-errors { margin-top: 12px; - + .error-line { font-size: 12px; color: #ef4444; margin-bottom: 4px; } - + .error-more { font-size: 12px; color: #888; @@ -1701,6 +1722,45 @@ $admin-danger: #ef4444; } } +// Import Section Mobile Styles +@media (max-width: 768px) { + .import-section { + grid-column: span 1; + } + + .import-controls { + flex-direction: column; + align-items: stretch; + gap: 12px; + } + + .file-input-wrapper { + width: 100%; + + .file-input-label { + width: 100%; + text-align: center; + box-sizing: border-box; + } + } + + .result-stats { + gap: 12px; + + .stat-item { + min-width: calc(33% - 8px); + + .stat-value { + font-size: 18px; + } + } + } + + .import-result { + padding: 12px; + } +} + // Prize Config Button & Modal .prize-config-btn { margin-top: 12px; diff --git a/packages/server/src/routes/admin.routes.ts b/packages/server/src/routes/admin.routes.ts index 69399a0..9e866e8 100644 --- a/packages/server/src/routes/admin.routes.ts +++ b/packages/server/src/routes/admin.routes.ts @@ -137,15 +137,17 @@ router.post('/participants/import', upload.single('file'), async (req, res, next /** * GET /api/admin/participants - * Get all participants + * Get all participants with statistics */ router.get('/participants', async (_req, res, next) => { try { const participants = participantService.getAll(); + const stats = participantService.getStats(); return res.json({ success: true, data: { count: participants.length, + tagDistribution: stats.tagDistribution, participants, }, }); diff --git a/packages/server/src/services/participant.service.ts b/packages/server/src/services/participant.service.ts index 9c18bb4..f3e19a3 100644 --- a/packages/server/src/services/participant.service.ts +++ b/packages/server/src/services/participant.service.ts @@ -232,6 +232,22 @@ class ParticipantService { return this.participants.size; } + /** + * Get statistics including tag distribution + */ + getStats(): { count: number; tagDistribution: Record } { + const tagDistribution: Record = {}; + for (const p of this.participants.values()) { + for (const tag of p.tags) { + tagDistribution[tag] = (tagDistribution[tag] || 0) + 1; + } + } + return { + count: this.participants.size, + tagDistribution, + }; + } + /** * Save participants to Redis for persistence */