From a442d050e4ef8a1459d22ef16dec97fb195b6071 Mon Sep 17 00:00:00 2001 From: empty Date: Fri, 23 Jan 2026 12:20:45 +0800 Subject: [PATCH] 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 --- package.json | 4 +- .../client-mobile/src/components/Postmark.vue | 61 +-- .../src/components/ProgramCard.vue | 168 ++++--- .../src/components/VotingDock.vue | 81 ++-- packages/client-mobile/src/utils/svgIcons.ts | 64 +++ .../src/components/PostcardGrid.vue | 2 +- .../src/components/PostcardItem.vue | 2 +- .../src/composables/useSocketClient.ts | 23 + .../src/pixi/HorseRaceRenderer.ts | 382 ++++++++++++++++ .../client-screen/src/pixi/LotteryMachine.ts | 25 +- packages/client-screen/src/router/index.ts | 6 + packages/client-screen/src/stores/admin.ts | 42 +- .../client-screen/src/views/AdminControl.vue | 415 +++++++++++++++++- .../client-screen/src/views/HorseRaceView.vue | 315 +++++++++++++ .../client-screen/src/views/LuckyDrawView.vue | 354 ++++++++------- packages/server/config/prizes.json | 42 ++ packages/server/package.json | 27 +- packages/server/src/index.ts | 11 + packages/server/src/routes/admin.routes.ts | 103 ++++- packages/server/src/services/admin.service.ts | 91 +++- .../src/services/participant.service.ts | 295 +++++++++++++ .../src/services/prize-config.service.ts | 150 +++++++ pnpm-lock.yaml | 185 ++++++++ 23 files changed, 2523 insertions(+), 325 deletions(-) create mode 100644 packages/client-mobile/src/utils/svgIcons.ts create mode 100644 packages/client-screen/src/pixi/HorseRaceRenderer.ts create mode 100644 packages/client-screen/src/views/HorseRaceView.vue create mode 100644 packages/server/config/prizes.json create mode 100644 packages/server/src/services/participant.service.ts create mode 100644 packages/server/src/services/prize-config.service.ts diff --git a/package.json b/package.json index d65a033..bfad374 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "packageManager": "pnpm@9.15.0", "type": "module", "scripts": { + "start": "pnpm -r --parallel run dev", + "stop": "for port in 3000 5173 5174; do lsof -ti :$port | xargs kill -9 2>/dev/null; done || true", "dev": "pnpm -r --parallel run dev", "dev:mobile": "pnpm --filter @gala/client-mobile dev", "dev:screen": "pnpm --filter @gala/client-screen dev", @@ -31,4 +33,4 @@ "node": ">=20.0.0", "pnpm": ">=9.0.0" } -} +} \ No newline at end of file diff --git a/packages/client-mobile/src/components/Postmark.vue b/packages/client-mobile/src/components/Postmark.vue index 73b5f98..ad88055 100644 --- a/packages/client-mobile/src/components/Postmark.vue +++ b/packages/client-mobile/src/components/Postmark.vue @@ -1,17 +1,19 @@ @@ -330,6 +434,37 @@ onUnmounted(() => {
限定: 属马
+ + + + + @@ -362,11 +497,29 @@ onUnmounted(() => { @click="stopAndReveal" > - {{ countdownDisplay }}s + {{ admin.stopButtonCountdown }}s 停止抽奖 + +
+ + +
+
+ +
+
+

🎯 抽奖名单导入

+ + {{ importResult.success ? `已导入 ${importResult.importedCount} 人` : '导入失败' }} + +
+ +
+
+ + +
+ +

支持格式:岗位 | 姓名 | 年份

+ + +
+
+
+ 总行数 + {{ importResult.totalCount }} +
+
+ 成功导入 + {{ importResult.importedCount }} +
+
+ {{ tagLabels[tag] || tag }} + {{ count }} +
+
+
+

{{ err }}

+

...还有 {{ importResult.errors.length - 5 }} 条错误

+
+
+
+
+
@@ -1375,4 +1585,169 @@ $admin-danger: #ef4444; } } } + +// Import Section Styles +.import-section { + grid-column: span 2; +} + +.import-controls { + display: flex; + gap: 16px; + align-items: center; + margin-bottom: 16px; +} + +.file-input-wrapper { + position: relative; + display: inline-block; + + .file-input { + position: absolute; + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; + } + + .file-input-label { + display: inline-block; + padding: 10px 20px; + background: #252525; + border: 1px dashed #444; + border-radius: 8px; + color: #888; + cursor: pointer; + transition: all 0.2s; + + &:hover { + border-color: #666; + color: #ccc; + } + } +} + +.import-hint { + font-size: 12px; + color: #666; + margin-bottom: 16px; +} + +.import-result { + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.3); + border-radius: 8px; + padding: 16px; + + &.error { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.3); + } +} + +.result-stats { + display: flex; + flex-wrap: wrap; + gap: 24px; + + .stat-item { + .stat-value { + font-size: 20px; + } + } +} + +.result-errors { + margin-top: 12px; + + .error-line { + font-size: 12px; + color: #ef4444; + margin-bottom: 4px; + } + + .error-more { + font-size: 12px; + color: #888; + font-style: italic; + } +} + +// Prize Config Button & Modal +.prize-config-btn { + margin-top: 12px; + padding: 6px 12px; + background: #333; + border: 1px solid #555; + border-radius: 6px; + color: #ccc; + font-size: 12px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: #444; + border-color: #666; + } +} + +.prize-config-modal { + width: 600px; + max-width: 90vw; +} + +.prize-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +.prize-edit-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background: #252525; + border-radius: 8px; +} + +.prize-round { + min-width: 60px; + font-weight: bold; + color: #f59e0b; +} + +.prize-input { + padding: 8px 12px; + background: #1a1a1a; + border: 1px solid #444; + border-radius: 6px; + color: #fff; + font-size: 14px; + + &:focus { + outline: none; + border-color: #f59e0b; + } +} + +.prize-input-wide { + flex: 1; +} + +.prize-input-small { + width: 60px; + text-align: center; +} + +.prize-unit { + color: #888; + font-size: 14px; +} + +.loading { + text-align: center; + color: #888; + padding: 20px; +} diff --git a/packages/client-screen/src/views/HorseRaceView.vue b/packages/client-screen/src/views/HorseRaceView.vue new file mode 100644 index 0000000..257a21d --- /dev/null +++ b/packages/client-screen/src/views/HorseRaceView.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/packages/client-screen/src/views/LuckyDrawView.vue b/packages/client-screen/src/views/LuckyDrawView.vue index c31e5eb..7dafc19 100644 --- a/packages/client-screen/src/views/LuckyDrawView.vue +++ b/packages/client-screen/src/views/LuckyDrawView.vue @@ -1,11 +1,13 @@