@quantabit/lottery-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1136 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var sdkConfig = require('@quantabit/sdk-config');
5
+ var React = require('react');
6
+
7
+ /**
8
+ * Lottery SDK - API 客户端
9
+ * 抽奖系统后端接口封装
10
+ *
11
+ * 使用 BaseApiClient 基类简化代码
12
+ */
13
+
14
+
15
+ /**
16
+ * 抽奖 API 客户端
17
+ */
18
+ class LotteryApiClient extends sdkConfig.BaseApiClient {
19
+ constructor(config = {}) {
20
+ super('/lottery', config);
21
+ }
22
+
23
+ // ============ 抽奖活动 ============
24
+
25
+ /**
26
+ * 获取进行中的抽奖活动
27
+ */
28
+ async getActiveLotteries() {
29
+ return this.get('/active');
30
+ }
31
+
32
+ /**
33
+ * 获取抽奖活动列表
34
+ * @param {Object} params - 查询参数
35
+ */
36
+ async getLotteries(params = {}) {
37
+ return this.get('/list', params);
38
+ }
39
+
40
+ /**
41
+ * 获取抽奖活动详情
42
+ * @param {string} lotteryId - 活动 ID
43
+ */
44
+ async getLottery(lotteryId) {
45
+ return this.get(`/${lotteryId}`);
46
+ }
47
+
48
+ /**
49
+ * 获取奖品列表
50
+ * @param {string} lotteryId - 活动 ID
51
+ */
52
+ async getPrizes(lotteryId) {
53
+ return this.get(`/${lotteryId}/prizes`);
54
+ }
55
+
56
+ // ============ 抽奖操作 ============
57
+
58
+ /**
59
+ * 执行抽奖
60
+ * @param {string} lotteryId - 活动 ID
61
+ * @param {number} times - 抽奖次数
62
+ */
63
+ async draw(lotteryId, times = 1) {
64
+ return this.post(`/${lotteryId}/draw`, {
65
+ times
66
+ });
67
+ }
68
+
69
+ /**
70
+ * 批量抽奖(十连抽等)
71
+ * @param {string} lotteryId - 活动 ID
72
+ * @param {number} count - 次数
73
+ */
74
+ async batchDraw(lotteryId, count) {
75
+ return this.post(`/${lotteryId}/draw/batch`, {
76
+ count
77
+ });
78
+ }
79
+
80
+ /**
81
+ * 获取抽奖资格
82
+ * @param {string} lotteryId - 活动 ID
83
+ */
84
+ async getDrawChances(lotteryId) {
85
+ return this.get(`/${lotteryId}/chances`);
86
+ }
87
+
88
+ /**
89
+ * 获取额外抽奖机会(观看广告等)
90
+ * @param {string} lotteryId - 活动 ID
91
+ * @param {string} method - 获取方式
92
+ */
93
+ async earnChance(lotteryId, method) {
94
+ return this.post(`/${lotteryId}/chances/earn`, {
95
+ method
96
+ });
97
+ }
98
+
99
+ // ============ 中奖记录 ============
100
+
101
+ /**
102
+ * 获取中奖记录
103
+ * @param {string} lotteryId - 活动 ID
104
+ * @param {Object} params - 查询参数
105
+ */
106
+ async getWinningRecords(lotteryId, params = {}) {
107
+ return this.get(`/${lotteryId}/records`, params);
108
+ }
109
+
110
+ /**
111
+ * 获取我的中奖记录
112
+ * @param {Object} params - 查询参数
113
+ */
114
+ async getMyWinnings(params = {}) {
115
+ return this.get('/my/winnings', params);
116
+ }
117
+
118
+ /**
119
+ * 获取最新中奖公告
120
+ * @param {string} lotteryId - 活动 ID
121
+ * @param {number} limit - 返回数量
122
+ */
123
+ async getLatestWinners(lotteryId, limit = 10) {
124
+ return this.get(`/${lotteryId}/winners/latest`, {
125
+ limit
126
+ });
127
+ }
128
+
129
+ // ============ 奖品领取 ============
130
+
131
+ /**
132
+ * 领取奖品
133
+ * @param {string} recordId - 中奖记录 ID
134
+ * @param {Object} receiveData - 领取信息
135
+ */
136
+ async claimPrize(recordId, receiveData = {}) {
137
+ return this.post(`/winnings/${recordId}/claim`, receiveData);
138
+ }
139
+
140
+ /**
141
+ * 获取待领取奖品
142
+ */
143
+ async getPendingPrizes() {
144
+ return this.get('/my/pending');
145
+ }
146
+
147
+ /**
148
+ * 获取奖品物流信息
149
+ * @param {string} recordId - 中奖记录 ID
150
+ */
151
+ async getPrizeShipping(recordId) {
152
+ return this.get(`/winnings/${recordId}/shipping`);
153
+ }
154
+
155
+ // ============ 活动统计 ============
156
+
157
+ /**
158
+ * 获取活动统计
159
+ * @param {string} lotteryId - 活动 ID
160
+ */
161
+ async getLotteryStats(lotteryId) {
162
+ return this.get(`/${lotteryId}/stats`);
163
+ }
164
+
165
+ /**
166
+ * 获取中奖概率(如果公开)
167
+ * @param {string} lotteryId - 活动 ID
168
+ */
169
+ async getOdds(lotteryId) {
170
+ return this.get(`/${lotteryId}/odds`);
171
+ }
172
+
173
+ // ============ 管理员操作 ============
174
+
175
+ /**
176
+ * 创建抽奖活动(管理员)
177
+ * @param {Object} data - 活动数据
178
+ */
179
+ async createLottery(data) {
180
+ return this.post('/admin', data);
181
+ }
182
+
183
+ /**
184
+ * 更新抽奖活动(管理员)
185
+ * @param {string} lotteryId - 活动 ID
186
+ * @param {Object} updates - 更新数据
187
+ */
188
+ async updateLottery(lotteryId, updates) {
189
+ return this.put(`/admin/${lotteryId}`, updates);
190
+ }
191
+
192
+ /**
193
+ * 获取活动统计(管理员)
194
+ * @param {string} lotteryId - 活动 ID
195
+ */
196
+ async getAdminStats(lotteryId) {
197
+ return this.get(`/admin/${lotteryId}/stats`);
198
+ }
199
+
200
+ // ============ 兼容性别名 ============
201
+
202
+ /**
203
+ * 获取我的奖品 (别名)
204
+ */
205
+ async getMyPrizes(params = {}) {
206
+ return this.getMyWinnings(params);
207
+ }
208
+
209
+ /**
210
+ * 获取中奖者 (别名)
211
+ */
212
+ async getWinners(lotteryId, limit = 10) {
213
+ return this.getLatestWinners(lotteryId, limit);
214
+ }
215
+ }
216
+
217
+ // 创建默认实例
218
+ const lotteryApi = new LotteryApiClient();
219
+
220
+ const LotteryType = {};
221
+ const PrizeLevel = {};
222
+
223
+ /**
224
+ * Lottery SDK - 国际化
225
+ * 抽奖系统多语言支持
226
+ */
227
+
228
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
229
+ const messages = {
230
+ zh: {
231
+ // 抽奖
232
+ lottery: '抽奖',
233
+ draw: '抽奖',
234
+ luckyDraw: '幸运抽奖',
235
+ // 类型
236
+ wheel: '转盘',
237
+ scratch: '刮刮卡',
238
+ slot: '老虎机',
239
+ card: '翻牌',
240
+ shake: '摇一摇',
241
+ // 操作
242
+ start: '开始抽奖',
243
+ draw: '抽奖',
244
+ spin: '转动',
245
+ scratch: '刮开',
246
+ flip: '翻牌',
247
+ shake: '摇一摇',
248
+ tryAgain: '再试一次',
249
+ // 机会
250
+ chances: '抽奖次数',
251
+ remaining: '剩余',
252
+ noChances: '暂无抽奖机会',
253
+ getMoreChances: '获取更多机会',
254
+ // 结果
255
+ congratulations: '恭喜!',
256
+ won: '获得',
257
+ thanksForPlaying: '谢谢参与',
258
+ noLuck: '未中奖',
259
+ prizeClaimed: '奖品已发放',
260
+ // 奖品
261
+ prize: '奖品',
262
+ prizes: '奖品列表',
263
+ myPrizes: '我的奖品',
264
+ grandPrize: '大奖',
265
+ firstPrize: '一等奖',
266
+ secondPrize: '二等奖',
267
+ thirdPrize: '三等奖',
268
+ consolation: '安慰奖',
269
+ // 概率
270
+ probability: '概率',
271
+ rare: '稀有',
272
+ normal: '普通',
273
+ // 规则
274
+ rules: '抽奖规则',
275
+ howToPlay: '玩法说明',
276
+ // 历史
277
+ history: '抽奖记录',
278
+ winners: '中奖名单',
279
+ recentWinners: '最近中奖',
280
+ // 状态
281
+ loading: '加载中...',
282
+ processing: '正在抽奖...',
283
+ // 时间
284
+ startsIn: '距开始',
285
+ endsIn: '距结束',
286
+ ended: '已结束',
287
+ // 活动
288
+ eventName: '活动名称',
289
+ eventPeriod: '活动时间'
290
+ },
291
+ en: {
292
+ lottery: 'Lottery',
293
+ draw: 'Draw',
294
+ luckyDraw: 'Lucky Draw',
295
+ wheel: 'Wheel',
296
+ scratch: 'Scratch Card',
297
+ slot: 'Slot Machine',
298
+ card: 'Card Flip',
299
+ shake: 'Shake',
300
+ start: 'Start',
301
+ spin: 'Spin',
302
+ flip: 'Flip',
303
+ tryAgain: 'Try Again',
304
+ chances: 'Chances',
305
+ remaining: 'remaining',
306
+ noChances: 'No chances left',
307
+ getMoreChances: 'Get More Chances',
308
+ congratulations: 'Congratulations!',
309
+ won: 'You won',
310
+ thanksForPlaying: 'Thanks for playing',
311
+ noLuck: 'No Luck',
312
+ prizeClaimed: 'Prize claimed',
313
+ prize: 'Prize',
314
+ prizes: 'Prizes',
315
+ myPrizes: 'My Prizes',
316
+ grandPrize: 'Grand Prize',
317
+ firstPrize: '1st Prize',
318
+ secondPrize: '2nd Prize',
319
+ thirdPrize: '3rd Prize',
320
+ consolation: 'Consolation',
321
+ probability: 'Probability',
322
+ rare: 'Rare',
323
+ normal: 'Normal',
324
+ rules: 'Rules',
325
+ howToPlay: 'How to Play',
326
+ history: 'History',
327
+ winners: 'Winners',
328
+ recentWinners: 'Recent Winners',
329
+ loading: 'Loading...',
330
+ processing: 'Drawing...',
331
+ startsIn: 'Starts in',
332
+ endsIn: 'Ends in',
333
+ ended: 'Ended',
334
+ eventName: 'Event Name',
335
+ eventPeriod: 'Event Period'
336
+ },
337
+ ja: {
338
+ lottery: '抽選',
339
+ draw: '抽選',
340
+ luckyDraw: 'ラッキードロー',
341
+ wheel: 'ルーレット',
342
+ scratch: 'スクラッチカード',
343
+ slot: 'スロット',
344
+ card: 'カードフリップ',
345
+ shake: 'シェイク',
346
+ start: 'スタート',
347
+ spin: '回す',
348
+ flip: 'めくる',
349
+ tryAgain: 'もう一度',
350
+ chances: '残り回数',
351
+ remaining: '回',
352
+ noChances: 'チャンスなし',
353
+ getMoreChances: 'チャンスを獲得',
354
+ congratulations: 'おめでとう!',
355
+ won: '獲得',
356
+ thanksForPlaying: 'ご参加ありがとう',
357
+ noLuck: '残念',
358
+ prizeClaimed: '賞品を獲得',
359
+ prize: '賞品',
360
+ prizes: '賞品リスト',
361
+ myPrizes: 'マイ賞品',
362
+ grandPrize: '大賞',
363
+ firstPrize: '1等',
364
+ secondPrize: '2等',
365
+ thirdPrize: '3等',
366
+ consolation: '参加賞',
367
+ probability: '確率',
368
+ rare: 'レア',
369
+ normal: 'ノーマル',
370
+ rules: 'ルール',
371
+ howToPlay: '遊び方',
372
+ history: '履歴',
373
+ winners: '当選者',
374
+ recentWinners: '最近の当選者',
375
+ loading: '読み込み中...',
376
+ processing: '抽選中...',
377
+ startsIn: '開始まで',
378
+ endsIn: '終了まで',
379
+ ended: '終了',
380
+ eventName: 'イベント名',
381
+ eventPeriod: 'イベント期間'
382
+ },
383
+ ko: {
384
+ lottery: '추첨',
385
+ draw: '추첨',
386
+ luckyDraw: '행운 추첨',
387
+ wheel: '룰렛',
388
+ scratch: '스크래치 카드',
389
+ slot: '슬롯',
390
+ card: '카드 뒤집기',
391
+ shake: '흔들기',
392
+ start: '시작',
393
+ spin: '돌리기',
394
+ flip: '뒤집기',
395
+ tryAgain: '다시 시도',
396
+ chances: '남은 기회',
397
+ remaining: '회',
398
+ noChances: '기회 없음',
399
+ getMoreChances: '기회 받기',
400
+ congratulations: '축하합니다!',
401
+ won: '획득',
402
+ thanksForPlaying: '참가해 주셔서 감사합니다',
403
+ noLuck: '아쉽네요',
404
+ prizeClaimed: '경품 획득',
405
+ prize: '경품',
406
+ prizes: '경품 목록',
407
+ myPrizes: '내 경품',
408
+ grandPrize: '대상',
409
+ firstPrize: '1등',
410
+ secondPrize: '2등',
411
+ thirdPrize: '3등',
412
+ consolation: '참가상',
413
+ probability: '확률',
414
+ rare: '레어',
415
+ normal: '일반',
416
+ rules: '규칙',
417
+ howToPlay: '플레이 방법',
418
+ history: '기록',
419
+ winners: '당첨자',
420
+ recentWinners: '최근 당첨자',
421
+ loading: '로딩 중...',
422
+ processing: '추첨 중...',
423
+ startsIn: '시작까지',
424
+ endsIn: '종료까지',
425
+ ended: '종료',
426
+ eventName: '이벤트 이름',
427
+ eventPeriod: '이벤트 기간'
428
+ }
429
+ };
430
+ let currentLanguage = 'zh';
431
+ function setLanguage(lang) {
432
+ if (SUPPORTED_LANGUAGES.includes(lang)) currentLanguage = lang;
433
+ }
434
+ function getLanguage() {
435
+ return currentLanguage;
436
+ }
437
+ function t(key) {
438
+ return (messages[currentLanguage] || messages.en)[key] || key;
439
+ }
440
+
441
+ /**
442
+ * Lottery SDK - React Hooks
443
+ * 抽奖系统相关的状态管理
444
+ */
445
+
446
+
447
+ /**
448
+ * 抽奖活动
449
+ */
450
+ function useLottery(lotteryId) {
451
+ const [lottery, setLottery] = React.useState(null);
452
+ const [chances, setChances] = React.useState(0);
453
+ const [loading, setLoading] = React.useState(true);
454
+ const [error, setError] = React.useState(null);
455
+ const fetchLottery = React.useCallback(async () => {
456
+ if (!lotteryId) return;
457
+ try {
458
+ setLoading(true);
459
+ const response = await lotteryApi.getLottery(lotteryId);
460
+ setLottery(response.lottery);
461
+ setChances(response.chances || 0);
462
+ setError(null);
463
+ } catch (err) {
464
+ setError(err.message);
465
+ } finally {
466
+ setLoading(false);
467
+ }
468
+ }, [lotteryId]);
469
+ React.useEffect(() => {
470
+ fetchLottery();
471
+ }, [fetchLottery]);
472
+ return {
473
+ lottery,
474
+ chances,
475
+ loading,
476
+ error,
477
+ refresh: fetchLottery
478
+ };
479
+ }
480
+
481
+ /**
482
+ * 抽奖
483
+ */
484
+ function useDraw(lotteryId) {
485
+ const [drawing, setDrawing] = React.useState(false);
486
+ const [result, setResult] = React.useState(null);
487
+ const [error, setError] = React.useState(null);
488
+ const draw = React.useCallback(async () => {
489
+ try {
490
+ setDrawing(true);
491
+ setResult(null);
492
+ setError(null);
493
+ const response = await lotteryApi.draw(lotteryId);
494
+ setResult(response);
495
+ return response;
496
+ } catch (err) {
497
+ setError(err.message);
498
+ throw err;
499
+ } finally {
500
+ setDrawing(false);
501
+ }
502
+ }, [lotteryId]);
503
+ const reset = React.useCallback(() => {
504
+ setResult(null);
505
+ setError(null);
506
+ }, []);
507
+ return {
508
+ draw,
509
+ drawing,
510
+ result,
511
+ error,
512
+ reset
513
+ };
514
+ }
515
+
516
+ /**
517
+ * 转盘抽奖
518
+ */
519
+ function useSpinWheel(prizes) {
520
+ const [spinning, setSpinning] = React.useState(false);
521
+ const [rotation, setRotation] = React.useState(0);
522
+ const [resultIndex, setResultIndex] = React.useState(null);
523
+ const spin = React.useCallback(targetIndex => {
524
+ if (spinning) return;
525
+ setSpinning(true);
526
+ setResultIndex(null);
527
+
528
+ // 计算旋转角度
529
+ const segments = prizes.length;
530
+ const segmentAngle = 360 / segments;
531
+ const extraRotations = 5; // 额外转5圈
532
+ const targetRotation = rotation + extraRotations * 360 + (segments - targetIndex) * segmentAngle + segmentAngle / 2;
533
+ setRotation(targetRotation);
534
+
535
+ // 动画完成后
536
+ setTimeout(() => {
537
+ setSpinning(false);
538
+ setResultIndex(targetIndex);
539
+ }, 4000);
540
+ }, [spinning, rotation, prizes.length]);
541
+ const reset = React.useCallback(() => {
542
+ setResultIndex(null);
543
+ }, []);
544
+ return {
545
+ spin,
546
+ spinning,
547
+ rotation,
548
+ resultIndex,
549
+ reset
550
+ };
551
+ }
552
+
553
+ /**
554
+ * 刮刮卡
555
+ */
556
+ function useScratchCard() {
557
+ const [scratched, setScratched] = React.useState(false);
558
+ const [scratchPercent, setScratchPercent] = React.useState(0);
559
+ const canvasRef = React.useRef(null);
560
+ const initCard = React.useCallback((canvas, coverImage) => {
561
+ if (!canvas) return;
562
+ const ctx = canvas.getContext('2d');
563
+ const img = new Image();
564
+ img.onload = () => {
565
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
566
+ };
567
+ img.src = coverImage || 'data:image/png;base64,...'; // 默认封面
568
+
569
+ canvasRef.current = canvas;
570
+ }, []);
571
+ const handleScratch = React.useCallback(e => {
572
+ if (!canvasRef.current) return;
573
+ const canvas = canvasRef.current;
574
+ const ctx = canvas.getContext('2d');
575
+ const rect = canvas.getBoundingClientRect();
576
+ const x = e.clientX - rect.left;
577
+ const y = e.clientY - rect.top;
578
+ ctx.globalCompositeOperation = 'destination-out';
579
+ ctx.beginPath();
580
+ ctx.arc(x, y, 20, 0, Math.PI * 2);
581
+ ctx.fill();
582
+
583
+ // 计算已刮开百分比
584
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
585
+ let scratched = 0;
586
+ for (let i = 3; i < imageData.data.length; i += 4) {
587
+ if (imageData.data[i] === 0) scratched++;
588
+ }
589
+ const percent = scratched / (imageData.data.length / 4) * 100;
590
+ setScratchPercent(percent);
591
+ if (percent > 50) {
592
+ setScratched(true);
593
+ }
594
+ }, []);
595
+ return {
596
+ scratched,
597
+ scratchPercent,
598
+ initCard,
599
+ handleScratch
600
+ };
601
+ }
602
+
603
+ /**
604
+ * 翻牌
605
+ */
606
+ function useCardFlip(cardCount = 9) {
607
+ const [flippedCards, setFlippedCards] = React.useState([]);
608
+ const [selectedCard, setSelectedCard] = React.useState(null);
609
+ const [revealed, setRevealed] = React.useState(false);
610
+ const flipCard = React.useCallback(index => {
611
+ if (revealed || flippedCards.includes(index)) return;
612
+ setSelectedCard(index);
613
+ setFlippedCards([index]);
614
+ }, [revealed, flippedCards]);
615
+ const revealAll = React.useCallback(() => {
616
+ setRevealed(true);
617
+ setFlippedCards(Array.from({
618
+ length: cardCount
619
+ }, (_, i) => i));
620
+ }, [cardCount]);
621
+ const reset = React.useCallback(() => {
622
+ setFlippedCards([]);
623
+ setSelectedCard(null);
624
+ setRevealed(false);
625
+ }, []);
626
+ return {
627
+ flippedCards,
628
+ selectedCard,
629
+ revealed,
630
+ flipCard,
631
+ revealAll,
632
+ reset
633
+ };
634
+ }
635
+
636
+ /**
637
+ * 抽奖历史
638
+ */
639
+ function useLotteryHistory(lotteryId) {
640
+ const [history, setHistory] = React.useState([]);
641
+ const [loading, setLoading] = React.useState(true);
642
+ const fetchHistory = React.useCallback(async () => {
643
+ try {
644
+ setLoading(true);
645
+ const response = await lotteryApi.getHistory(lotteryId);
646
+ setHistory(response.data || []);
647
+ } catch (err) {
648
+ console.error('Get history error:', err);
649
+ } finally {
650
+ setLoading(false);
651
+ }
652
+ }, [lotteryId]);
653
+ React.useEffect(() => {
654
+ fetchHistory();
655
+ }, [fetchHistory]);
656
+ return {
657
+ history,
658
+ loading,
659
+ refresh: fetchHistory
660
+ };
661
+ }
662
+
663
+ /**
664
+ * 中奖名单
665
+ */
666
+ function useWinners(lotteryId) {
667
+ const [winners, setWinners] = React.useState([]);
668
+ const [loading, setLoading] = React.useState(true);
669
+ const fetchWinners = React.useCallback(async () => {
670
+ try {
671
+ setLoading(true);
672
+ const response = await lotteryApi.getWinners(lotteryId);
673
+ setWinners(response.data || []);
674
+ } catch (err) {
675
+ console.error('Get winners error:', err);
676
+ } finally {
677
+ setLoading(false);
678
+ }
679
+ }, [lotteryId]);
680
+ React.useEffect(() => {
681
+ fetchWinners();
682
+ }, [fetchWinners]);
683
+ return {
684
+ winners,
685
+ loading,
686
+ refresh: fetchWinners
687
+ };
688
+ }
689
+
690
+ /**
691
+ * 我的奖品
692
+ */
693
+ function useMyPrizes() {
694
+ const [prizes, setPrizes] = React.useState([]);
695
+ const [loading, setLoading] = React.useState(true);
696
+ const fetchPrizes = React.useCallback(async () => {
697
+ try {
698
+ setLoading(true);
699
+ const response = await lotteryApi.getMyPrizes();
700
+ setPrizes(response.data || []);
701
+ } catch (err) {
702
+ console.error('Get my prizes error:', err);
703
+ } finally {
704
+ setLoading(false);
705
+ }
706
+ }, []);
707
+ React.useEffect(() => {
708
+ fetchPrizes();
709
+ }, [fetchPrizes]);
710
+ const claimPrize = React.useCallback(async prizeId => {
711
+ try {
712
+ await lotteryApi.claimPrize(prizeId);
713
+ setPrizes(prev => prev.map(p => p.id === prizeId ? {
714
+ ...p,
715
+ claimed: true
716
+ } : p));
717
+ } catch (err) {
718
+ console.error('Claim prize error:', err);
719
+ throw err;
720
+ }
721
+ }, []);
722
+ return {
723
+ prizes,
724
+ loading,
725
+ claimPrize,
726
+ refresh: fetchPrizes
727
+ };
728
+ }
729
+
730
+ /**
731
+ * 倒计时
732
+ */
733
+ function useLotteryCountdown(endTime) {
734
+ const [remaining, setRemaining] = React.useState({
735
+ days: 0,
736
+ hours: 0,
737
+ minutes: 0,
738
+ seconds: 0
739
+ });
740
+ const [isEnded, setIsEnded] = React.useState(false);
741
+ React.useEffect(() => {
742
+ if (!endTime) return;
743
+ const updateCountdown = () => {
744
+ const now = Date.now();
745
+ const end = new Date(endTime).getTime();
746
+ const diff = end - now;
747
+ if (diff <= 0) {
748
+ setIsEnded(true);
749
+ setRemaining({
750
+ days: 0,
751
+ hours: 0,
752
+ minutes: 0,
753
+ seconds: 0
754
+ });
755
+ return;
756
+ }
757
+ setRemaining({
758
+ days: Math.floor(diff / (1000 * 60 * 60 * 24)),
759
+ hours: Math.floor(diff % (1000 * 60 * 60 * 24) / (1000 * 60 * 60)),
760
+ minutes: Math.floor(diff % (1000 * 60 * 60) / (1000 * 60)),
761
+ seconds: Math.floor(diff % (1000 * 60) / 1000)
762
+ });
763
+ };
764
+ updateCountdown();
765
+ const timer = setInterval(updateCountdown, 1000);
766
+ return () => clearInterval(timer);
767
+ }, [endTime]);
768
+ return {
769
+ remaining,
770
+ isEnded
771
+ };
772
+ }
773
+
774
+ /**
775
+ * Lottery SDK - React 组件
776
+ * 抽奖系统可视化组件
777
+ */
778
+
779
+
780
+ /**
781
+ * 转盘抽奖
782
+ */
783
+ function SpinWheel({
784
+ lotteryId,
785
+ prizes,
786
+ onResult
787
+ }) {
788
+ const {
789
+ chances,
790
+ refresh
791
+ } = useLottery(lotteryId);
792
+ const {
793
+ draw,
794
+ drawing
795
+ } = useDraw(lotteryId);
796
+ const {
797
+ spin,
798
+ spinning,
799
+ rotation,
800
+ resultIndex,
801
+ reset
802
+ } = useSpinWheel(prizes);
803
+ const handleSpin = React.useCallback(async () => {
804
+ if (spinning || drawing || chances <= 0) return;
805
+ try {
806
+ const result = await draw();
807
+ // 找到获奖奖品的索引
808
+ const prizeIndex = prizes.findIndex(p => p.id === result.prizeId);
809
+ spin(prizeIndex >= 0 ? prizeIndex : 0);
810
+
811
+ // 动画完成后回调
812
+ setTimeout(() => {
813
+ onResult?.(result);
814
+ refresh();
815
+ }, 4500);
816
+ } catch (err) {
817
+ console.error('Draw error:', err);
818
+ }
819
+ }, [spinning, drawing, chances, draw, spin, prizes, onResult, refresh]);
820
+ return /*#__PURE__*/React.createElement("div", {
821
+ className: "eco-lottery-wheel"
822
+ }, /*#__PURE__*/React.createElement("div", {
823
+ className: "eco-lottery-wheel-container"
824
+ }, /*#__PURE__*/React.createElement("div", {
825
+ className: "eco-lottery-wheel-disc",
826
+ style: {
827
+ transform: `rotate(${rotation}deg)`
828
+ }
829
+ }, prizes.map((prize, qbit) => {
830
+ const angle = 360 / prizes.length * qbit;
831
+ return /*#__PURE__*/React.createElement("div", {
832
+ key: prize.id,
833
+ className: "eco-lottery-wheel-segment",
834
+ style: {
835
+ transform: `rotate(${angle}deg)`,
836
+ backgroundColor: qbit % 2 === 0 ? '#6366f1' : '#8b5cf6'
837
+ }
838
+ }, /*#__PURE__*/React.createElement("span", {
839
+ className: "eco-lottery-wheel-prize"
840
+ }, prize.name));
841
+ })), /*#__PURE__*/React.createElement("div", {
842
+ className: "eco-lottery-wheel-pointer"
843
+ }, "\u25BC")), /*#__PURE__*/React.createElement("div", {
844
+ className: "eco-lottery-chances"
845
+ }, t('chances'), ": ", /*#__PURE__*/React.createElement("strong", null, chances)), /*#__PURE__*/React.createElement("button", {
846
+ className: "eco-lottery-spin-btn",
847
+ onClick: handleSpin,
848
+ disabled: spinning || drawing || chances <= 0
849
+ }, spinning ? t('processing') : t('spin')));
850
+ }
851
+
852
+ /**
853
+ * 翻牌抽奖
854
+ */
855
+ function CardFlipGame({
856
+ lotteryId,
857
+ cardCount = 9,
858
+ prizes,
859
+ onResult
860
+ }) {
861
+ const {
862
+ chances,
863
+ refresh
864
+ } = useLottery(lotteryId);
865
+ const {
866
+ draw,
867
+ drawing
868
+ } = useDraw(lotteryId);
869
+ const {
870
+ flippedCards,
871
+ selectedCard,
872
+ flipCard,
873
+ revealAll,
874
+ reset
875
+ } = useCardFlip(cardCount);
876
+ const [result, setResult] = React.useState(null);
877
+ const handleFlip = React.useCallback(async index => {
878
+ if (drawing || chances <= 0 || flippedCards.length > 0) return;
879
+ try {
880
+ const drawResult = await draw();
881
+ setResult(drawResult);
882
+ flipCard(index);
883
+ setTimeout(() => {
884
+ revealAll();
885
+ onResult?.(drawResult);
886
+ refresh();
887
+ }, 1500);
888
+ } catch (err) {
889
+ console.error('Draw error:', err);
890
+ }
891
+ }, [drawing, chances, flippedCards, draw, flipCard, revealAll, onResult, refresh]);
892
+ const handleReset = React.useCallback(() => {
893
+ reset();
894
+ setResult(null);
895
+ }, [reset]);
896
+ return /*#__PURE__*/React.createElement("div", {
897
+ className: "eco-lottery-cards"
898
+ }, /*#__PURE__*/React.createElement("div", {
899
+ className: "eco-lottery-cards-grid"
900
+ }, Array.from({
901
+ length: cardCount
902
+ }).map((_, qbit) => {
903
+ const isFlipped = flippedCards.includes(qbit);
904
+ const isSelected = selectedCard === qbit;
905
+ const prizeForCard = isSelected ? result?.prize : prizes[qbit % prizes.length];
906
+ return /*#__PURE__*/React.createElement("div", {
907
+ key: qbit,
908
+ className: `eco-lottery-card ${isFlipped ? 'flipped' : ''} ${isSelected ? 'selected' : ''}`,
909
+ onClick: () => handleFlip(qbit)
910
+ }, /*#__PURE__*/React.createElement("div", {
911
+ className: "eco-lottery-card-inner"
912
+ }, /*#__PURE__*/React.createElement("div", {
913
+ className: "eco-lottery-card-front"
914
+ }, "?"), /*#__PURE__*/React.createElement("div", {
915
+ className: "eco-lottery-card-back"
916
+ }, isFlipped && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", {
917
+ src: prizeForCard?.image || '/prize.png',
918
+ alt: prizeForCard?.name
919
+ }), /*#__PURE__*/React.createElement("span", null, prizeForCard?.name)))));
920
+ })), /*#__PURE__*/React.createElement("div", {
921
+ className: "eco-lottery-chances"
922
+ }, t('chances'), ": ", /*#__PURE__*/React.createElement("strong", null, chances)), flippedCards.length > 0 && /*#__PURE__*/React.createElement("button", {
923
+ className: "eco-lottery-reset-btn",
924
+ onClick: handleReset
925
+ }, t('tryAgain')));
926
+ }
927
+
928
+ /**
929
+ * 抽奖结果弹窗
930
+ */
931
+ function LotteryResultModal({
932
+ result,
933
+ onClose
934
+ }) {
935
+ if (!result) return null;
936
+ const isWin = result.prize && !result.prize.isConsolation;
937
+ return /*#__PURE__*/React.createElement("div", {
938
+ className: "eco-lottery-result-modal"
939
+ }, /*#__PURE__*/React.createElement("div", {
940
+ className: "eco-lottery-result-overlay",
941
+ onClick: onClose
942
+ }), /*#__PURE__*/React.createElement("div", {
943
+ className: "eco-lottery-result-content"
944
+ }, /*#__PURE__*/React.createElement("div", {
945
+ className: `eco-lottery-result-icon ${isWin ? 'win' : 'lose'}`
946
+ }, isWin ? '🎉' : '😢'), /*#__PURE__*/React.createElement("h3", null, isWin ? t('congratulations') : t('thanksForPlaying')), result.prize ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("img", {
947
+ src: result.prize.image || '/prize.png',
948
+ alt: result.prize.name
949
+ }), /*#__PURE__*/React.createElement("p", null, t('won'), ": ", /*#__PURE__*/React.createElement("strong", null, result.prize.name))) : /*#__PURE__*/React.createElement("p", null, t('noLuck')), /*#__PURE__*/React.createElement("button", {
950
+ onClick: onClose
951
+ }, t('tryAgain'))));
952
+ }
953
+
954
+ /**
955
+ * 中奖名单滚动
956
+ */
957
+ function WinnersMarquee({
958
+ lotteryId
959
+ }) {
960
+ const {
961
+ winners
962
+ } = useWinners(lotteryId);
963
+ if (winners.length === 0) return null;
964
+ return /*#__PURE__*/React.createElement("div", {
965
+ className: "eco-lottery-marquee"
966
+ }, /*#__PURE__*/React.createElement("span", {
967
+ className: "eco-lottery-marquee-label"
968
+ }, "\uD83C\uDFC6 ", t('recentWinners')), /*#__PURE__*/React.createElement("div", {
969
+ className: "eco-lottery-marquee-content"
970
+ }, /*#__PURE__*/React.createElement("div", {
971
+ className: "eco-lottery-marquee-track"
972
+ }, [...winners, ...winners].map((w, qbit) => /*#__PURE__*/React.createElement("span", {
973
+ key: qbit,
974
+ className: "eco-lottery-marquee-item"
975
+ }, w.userName, " \u83B7\u5F97 ", w.prizeName)))));
976
+ }
977
+
978
+ /**
979
+ * 奖品列表
980
+ */
981
+ function PrizeList({
982
+ prizes
983
+ }) {
984
+ return /*#__PURE__*/React.createElement("div", {
985
+ className: "eco-lottery-prizes"
986
+ }, /*#__PURE__*/React.createElement("h4", null, t('prizes')), /*#__PURE__*/React.createElement("div", {
987
+ className: "eco-lottery-prizes-grid"
988
+ }, prizes.map(prize => /*#__PURE__*/React.createElement("div", {
989
+ key: prize.id,
990
+ className: `eco-lottery-prize ${prize.rare ? 'rare' : ''}`
991
+ }, /*#__PURE__*/React.createElement("img", {
992
+ src: prize.image || '/prize.png',
993
+ alt: prize.name
994
+ }), /*#__PURE__*/React.createElement("span", {
995
+ className: "eco-lottery-prize-name"
996
+ }, prize.name), prize.probability && /*#__PURE__*/React.createElement("span", {
997
+ className: "eco-lottery-prize-prob"
998
+ }, prize.probability, "%")))));
999
+ }
1000
+
1001
+ /**
1002
+ * 我的奖品
1003
+ */
1004
+ function MyPrizesList() {
1005
+ const {
1006
+ prizes,
1007
+ loading,
1008
+ claimPrize
1009
+ } = useMyPrizes();
1010
+ if (loading) {
1011
+ return /*#__PURE__*/React.createElement("div", {
1012
+ className: "eco-lottery-loading"
1013
+ }, /*#__PURE__*/React.createElement("div", {
1014
+ className: "eco-lottery-spinner"
1015
+ }));
1016
+ }
1017
+ if (prizes.length === 0) {
1018
+ return /*#__PURE__*/React.createElement("div", {
1019
+ className: "eco-lottery-empty"
1020
+ }, /*#__PURE__*/React.createElement("span", null, "\uD83C\uDF81"), /*#__PURE__*/React.createElement("p", null, "\u6682\u65E0\u5956\u54C1"));
1021
+ }
1022
+ return /*#__PURE__*/React.createElement("div", {
1023
+ className: "eco-lottery-my-prizes"
1024
+ }, /*#__PURE__*/React.createElement("h4", null, t('myPrizes')), prizes.map(prize => /*#__PURE__*/React.createElement("div", {
1025
+ key: prize.id,
1026
+ className: "eco-lottery-my-prize"
1027
+ }, /*#__PURE__*/React.createElement("img", {
1028
+ src: prize.image || '/prize.png',
1029
+ alt: prize.name
1030
+ }), /*#__PURE__*/React.createElement("div", {
1031
+ className: "eco-lottery-my-prize-info"
1032
+ }, /*#__PURE__*/React.createElement("span", {
1033
+ className: "eco-lottery-my-prize-name"
1034
+ }, prize.name), /*#__PURE__*/React.createElement("span", {
1035
+ className: "eco-lottery-my-prize-time"
1036
+ }, new Date(prize.wonAt).toLocaleDateString())), !prize.claimed ? /*#__PURE__*/React.createElement("button", {
1037
+ onClick: () => claimPrize(prize.id)
1038
+ }, "\u9886\u53D6") : /*#__PURE__*/React.createElement("span", {
1039
+ className: "eco-lottery-claimed"
1040
+ }, t('prizeClaimed')))));
1041
+ }
1042
+
1043
+ /**
1044
+ * 倒计时
1045
+ */
1046
+ function LotteryCountdown({
1047
+ endTime
1048
+ }) {
1049
+ const {
1050
+ remaining,
1051
+ isEnded
1052
+ } = useLotteryCountdown(endTime);
1053
+ if (isEnded) {
1054
+ return /*#__PURE__*/React.createElement("div", {
1055
+ className: "eco-lottery-countdown ended"
1056
+ }, t('ended'));
1057
+ }
1058
+ return /*#__PURE__*/React.createElement("div", {
1059
+ className: "eco-lottery-countdown"
1060
+ }, /*#__PURE__*/React.createElement("span", {
1061
+ className: "eco-lottery-countdown-label"
1062
+ }, t('endsIn')), /*#__PURE__*/React.createElement("div", {
1063
+ className: "eco-lottery-countdown-timer"
1064
+ }, /*#__PURE__*/React.createElement("div", {
1065
+ className: "eco-lottery-countdown-item"
1066
+ }, /*#__PURE__*/React.createElement("span", {
1067
+ className: "eco-lottery-countdown-value"
1068
+ }, remaining.days), /*#__PURE__*/React.createElement("span", {
1069
+ className: "eco-lottery-countdown-unit"
1070
+ }, "\u5929")), /*#__PURE__*/React.createElement("span", {
1071
+ className: "eco-lottery-countdown-sep"
1072
+ }, ":"), /*#__PURE__*/React.createElement("div", {
1073
+ className: "eco-lottery-countdown-item"
1074
+ }, /*#__PURE__*/React.createElement("span", {
1075
+ className: "eco-lottery-countdown-value"
1076
+ }, String(remaining.hours).padStart(2, '0')), /*#__PURE__*/React.createElement("span", {
1077
+ className: "eco-lottery-countdown-unit"
1078
+ }, "\u65F6")), /*#__PURE__*/React.createElement("span", {
1079
+ className: "eco-lottery-countdown-sep"
1080
+ }, ":"), /*#__PURE__*/React.createElement("div", {
1081
+ className: "eco-lottery-countdown-item"
1082
+ }, /*#__PURE__*/React.createElement("span", {
1083
+ className: "eco-lottery-countdown-value"
1084
+ }, String(remaining.minutes).padStart(2, '0')), /*#__PURE__*/React.createElement("span", {
1085
+ className: "eco-lottery-countdown-unit"
1086
+ }, "\u5206")), /*#__PURE__*/React.createElement("span", {
1087
+ className: "eco-lottery-countdown-sep"
1088
+ }, ":"), /*#__PURE__*/React.createElement("div", {
1089
+ className: "eco-lottery-countdown-item"
1090
+ }, /*#__PURE__*/React.createElement("span", {
1091
+ className: "eco-lottery-countdown-value"
1092
+ }, String(remaining.seconds).padStart(2, '0')), /*#__PURE__*/React.createElement("span", {
1093
+ className: "eco-lottery-countdown-unit"
1094
+ }, "\u79D2"))));
1095
+ }
1096
+
1097
+ /**
1098
+ * @quantabit/lottery-sdk
1099
+ * Lottery System SDK - Full Version
1100
+ */
1101
+
1102
+ const getLottery = (...args) => lotteryApi.getLottery(...args);
1103
+ const draw = (...args) => lotteryApi.draw(...args);
1104
+ const getMyPrizes = (...args) => lotteryApi.getMyPrizes(...args);
1105
+ const getWinners = (...args) => lotteryApi.getWinners(...args);
1106
+
1107
+ exports.CardFlipGame = CardFlipGame;
1108
+ exports.LotteryApiClient = LotteryApiClient;
1109
+ exports.LotteryCountdown = LotteryCountdown;
1110
+ exports.LotteryResultModal = LotteryResultModal;
1111
+ exports.LotteryType = LotteryType;
1112
+ exports.MyPrizesList = MyPrizesList;
1113
+ exports.PrizeLevel = PrizeLevel;
1114
+ exports.PrizeList = PrizeList;
1115
+ exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES;
1116
+ exports.SpinWheel = SpinWheel;
1117
+ exports.WinnersMarquee = WinnersMarquee;
1118
+ exports.draw = draw;
1119
+ exports.getLanguage = getLanguage;
1120
+ exports.getLottery = getLottery;
1121
+ exports.getMyPrizes = getMyPrizes;
1122
+ exports.getWinners = getWinners;
1123
+ exports.lotteryApi = lotteryApi;
1124
+ exports.messages = messages;
1125
+ exports.setLanguage = setLanguage;
1126
+ exports.t = t;
1127
+ exports.useCardFlip = useCardFlip;
1128
+ exports.useDraw = useDraw;
1129
+ exports.useLottery = useLottery;
1130
+ exports.useLotteryCountdown = useLotteryCountdown;
1131
+ exports.useLotteryHistory = useLotteryHistory;
1132
+ exports.useMyPrizes = useMyPrizes;
1133
+ exports.useScratchCard = useScratchCard;
1134
+ exports.useSpinWheel = useSpinWheel;
1135
+ exports.useWinners = useWinners;
1136
+ //# sourceMappingURL=index.cjs.map