@quantabit/points-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,2006 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var sdkConfig = require('@quantabit/sdk-config');
5
+
6
+ /**
7
+ * Points SDK - API 客户端
8
+ * 积分系统后端接口封装
9
+ *
10
+ * 对接 QuantaBit 后端真实 API:
11
+ * - /points/balance - 积分余额
12
+ * - /points/add - 增加积分
13
+ * - /points/deduct - 扣减积分
14
+ * - /points/logs - 积分变动日志
15
+ *
16
+ * 使用 BaseApiClient 基类简化代码
17
+ */
18
+
19
+
20
+ /**
21
+ * 积分 API 客户端
22
+ *
23
+ * 支持多种积分类型:
24
+ * - general: 通用积分
25
+ * - vip: VIP 积分
26
+ * - activity: 活动积分
27
+ */
28
+ class PointsApiClient extends sdkConfig.BaseApiClient {
29
+ constructor(config = {}) {
30
+ super('', config);
31
+
32
+ // 当前用户身份信息
33
+ this._currentDid = null;
34
+ this._currentUserId = null;
35
+ this._platformInfo = null;
36
+
37
+ // 默认积分类型
38
+ this._defaultPointsType = 'general';
39
+ }
40
+
41
+ /**
42
+ * 设置当前用户 DID
43
+ * @param {string} did - 用户的 DID 标识
44
+ */
45
+ setCurrentDid(did) {
46
+ this._currentDid = did;
47
+ }
48
+
49
+ /**
50
+ * 设置当前用户 ID
51
+ * @param {number} userId - 用户 ID
52
+ */
53
+ setCurrentUserId(userId) {
54
+ this._currentUserId = userId;
55
+ }
56
+
57
+ /**
58
+ * 设置平台信息(用于跨平台调用)
59
+ * @param {string} platformId - 平台标识
60
+ * @param {string} platformUserId - 平台内用户 ID
61
+ */
62
+ setPlatformInfo(platformId, platformUserId) {
63
+ this._platformInfo = {
64
+ platform_id: platformId,
65
+ platform_user_id: platformUserId
66
+ };
67
+ }
68
+
69
+ /**
70
+ * 设置默认积分类型
71
+ * @param {string} type - 积分类型(general/vip/activity)
72
+ */
73
+ setDefaultPointsType(type) {
74
+ this._defaultPointsType = type;
75
+ }
76
+
77
+ /**
78
+ * 获取用户标识参数
79
+ * @private
80
+ */
81
+ _getUserIdentifier() {
82
+ if (this._currentDid) {
83
+ return {
84
+ did: this._currentDid
85
+ };
86
+ }
87
+ if (this._currentUserId) {
88
+ return {
89
+ user_id: this._currentUserId
90
+ };
91
+ }
92
+ if (this._platformInfo) {
93
+ return {
94
+ platform_user_id: this._platformInfo.platform_user_id,
95
+ source_platform: this._platformInfo.platform_id
96
+ };
97
+ }
98
+ return {};
99
+ }
100
+
101
+ // ============ 积分查询 ============
102
+
103
+ /**
104
+ * 获取积分余额
105
+ * @param {string} pointsType - 积分类型(可选,默认使用默认类型)
106
+ * @returns {Promise<{code: number, data: {points_type: string, balance: number, frozen: number, available: number, total_earned: number, total_used: number}}>}
107
+ */
108
+ async getBalance(pointsType = null) {
109
+ const params = {
110
+ ...this._getUserIdentifier(),
111
+ points_type: pointsType || this._defaultPointsType
112
+ };
113
+ return this.get('/points/balance', params);
114
+ }
115
+
116
+ /**
117
+ * 获取所有积分类型的余额
118
+ * @returns {Promise<Array<{points_type: string, balance: number}>>}
119
+ */
120
+ async getAllBalances() {
121
+ const types = ['general', 'vip', 'activity'];
122
+ const results = await Promise.all(types.map(type => this.getBalance(type)));
123
+ return results.map((result, index) => ({
124
+ points_type: types[index],
125
+ ...result.data
126
+ }));
127
+ }
128
+
129
+ /**
130
+ * 获取积分统计
131
+ * @returns {Promise<{code: number, data: Object}>}
132
+ */
133
+ async getStats() {
134
+ return this.getBalance();
135
+ }
136
+
137
+ // ============ 积分变动记录 ============
138
+
139
+ /**
140
+ * 获取积分变动日志
141
+ * @param {Object} params - 查询参数
142
+ * @param {string} params.points_type - 积分类型
143
+ * @param {string} params.change_type - 变动类型(earn/use/expire/adjust)
144
+ * @param {string} params.start_date - 开始日期
145
+ * @param {string} params.end_date - 结束日期
146
+ * @param {number} params.page - 页码
147
+ * @param {number} params.page_size - 每页数量
148
+ * @returns {Promise<{code: number, data: {list: Array, total: number}}>}
149
+ */
150
+ async getTransactions(params = {}) {
151
+ const queryParams = {
152
+ ...this._getUserIdentifier(),
153
+ points_type: params.points_type || this._defaultPointsType,
154
+ ...params
155
+ };
156
+ return this.get('/points/logs', queryParams);
157
+ }
158
+
159
+ /**
160
+ * 获取即将过期的积分
161
+ * @param {number} days - 天数
162
+ * @returns {Promise<{code: number, data: {expiring_points: number, expire_date: string}}>}
163
+ */
164
+ async getExpiringPoints(days = 30) {
165
+ const params = {
166
+ ...this._getUserIdentifier(),
167
+ days
168
+ };
169
+ return this.get('/points/expiring', params);
170
+ }
171
+
172
+ // ============ 积分获取 ============
173
+
174
+ /**
175
+ * 增加积分
176
+ * 用于任务完成、活动奖励等场景
177
+ *
178
+ * @param {Object} data - 增加数据
179
+ * @param {number} data.amount - 积分数量(必须大于 0)
180
+ * @param {string} data.points_type - 积分类型
181
+ * @param {string} data.source_platform - 来源平台
182
+ * @param {string} data.source_id - 来源 ID(如任务 ID)
183
+ * @param {string} data.description - 描述
184
+ * @returns {Promise<{code: number, data: {balance: number}}>}
185
+ */
186
+ async addPoints(data) {
187
+ const payload = {
188
+ ...this._getUserIdentifier(),
189
+ amount: data.amount,
190
+ points_type: data.points_type || this._defaultPointsType,
191
+ source_platform: data.source_platform || 'qbit-did',
192
+ source_id: data.source_id,
193
+ description: data.description || '积分奖励'
194
+ };
195
+ return this.post('/points/add', payload);
196
+ }
197
+
198
+ /**
199
+ * 完成积分任务并获得奖励
200
+ * @param {string} taskId - 任务 ID
201
+ * @param {Object} data - 任务数据
202
+ */
203
+ async completeEarnTask(taskId, data = {}) {
204
+ return this.addPoints({
205
+ source_id: taskId,
206
+ source_platform: data.source_platform,
207
+ amount: data.points || data.amount,
208
+ description: data.description || `完成任务 ${taskId}`
209
+ });
210
+ }
211
+
212
+ // ============ 积分使用 ============
213
+
214
+ /**
215
+ * 扣减积分
216
+ * 用于订单抵扣、商品兑换等场景
217
+ *
218
+ * @param {Object} data - 扣减数据
219
+ * @param {number} data.amount - 积分数量(必须大于 0)
220
+ * @param {string} data.points_type - 积分类型
221
+ * @param {string} data.source_platform - 来源平台
222
+ * @param {string} data.source_id - 来源 ID(如订单 ID)
223
+ * @param {string} data.description - 描述
224
+ * @returns {Promise<{code: number, data: {deducted: number, balance: number}} | {code: number, msg: string, data: {available: number, required: number}}>}
225
+ */
226
+ async deductPoints(data) {
227
+ const payload = {
228
+ ...this._getUserIdentifier(),
229
+ amount: data.amount,
230
+ points_type: data.points_type || this._defaultPointsType,
231
+ source_platform: data.source_platform || 'qbit-did',
232
+ source_id: data.source_id,
233
+ description: data.description || '积分抵扣'
234
+ };
235
+ return this.post('/points/deduct', payload);
236
+ }
237
+
238
+ /**
239
+ * 积分抵扣(别名方法)
240
+ * @param {number} points - 积分数量
241
+ * @param {Object} orderInfo - 订单信息
242
+ */
243
+ async deduct(points, orderInfo) {
244
+ return this.deductPoints({
245
+ amount: points,
246
+ source_id: orderInfo.order_id,
247
+ source_platform: orderInfo.source_platform,
248
+ description: orderInfo.description
249
+ });
250
+ }
251
+
252
+ // ============ 积分兑换 ============
253
+
254
+ /**
255
+ * 获取可兑换商品
256
+ * @param {Object} params - 查询参数
257
+ */
258
+ async getRedeemItems(params = {}) {
259
+ return this.get('/points/redeem/items', params);
260
+ }
261
+
262
+ /**
263
+ * 兑换商品
264
+ * @param {string} itemId - 商品 ID
265
+ * @param {Object} data - 兑换数据
266
+ */
267
+ async redeemItem(itemId, data = {}) {
268
+ const payload = {
269
+ ...this._getUserIdentifier(),
270
+ item_id: itemId,
271
+ ...data
272
+ };
273
+ return this.post('/points/redeem', payload);
274
+ }
275
+
276
+ /**
277
+ * 获取兑换记录
278
+ * @param {Object} params - 查询参数
279
+ */
280
+ async getRedeemHistory(params = {}) {
281
+ const queryParams = {
282
+ ...this._getUserIdentifier(),
283
+ ...params
284
+ };
285
+ return this.get('/points/redeem/history', queryParams);
286
+ }
287
+
288
+ // ============ 链上证明 ============
289
+
290
+ /**
291
+ * 提交积分变动链上交易哈希
292
+ * @param {string} pointsId - 积分来源/流水 ID
293
+ * @param {string} txHash - QBit 主网交易哈希
294
+ * @param {Object} data - 附加提交数据
295
+ */
296
+ async submitChainTx(pointsId, txHash, data = {}) {
297
+ return this.post(`/points/${pointsId}/chain/submit`, {
298
+ tx_hash: txHash,
299
+ chain: 'qbit',
300
+ network: 'mainnet',
301
+ ...data
302
+ });
303
+ }
304
+
305
+ /**
306
+ * 查询积分变动链上确认状态
307
+ * @param {string} pointsId - 积分来源/流水 ID
308
+ */
309
+ async getChainStatus(pointsId) {
310
+ return this.get(`/points/${pointsId}/chain/status`);
311
+ }
312
+
313
+ // ============ 积分等级 ============
314
+
315
+ /**
316
+ * 获取用户积分等级信息
317
+ */
318
+ async getLevelInfo() {
319
+ const params = this._getUserIdentifier();
320
+ return this.get('/points/level', params);
321
+ }
322
+
323
+ /**
324
+ * 获取等级规则
325
+ */
326
+ async getLevelRules() {
327
+ return this.get('/points/level/rules');
328
+ }
329
+
330
+ /**
331
+ * 获取等级权益
332
+ */
333
+ async getLevelBenefits() {
334
+ const params = this._getUserIdentifier();
335
+ return this.get('/points/level/benefits', params);
336
+ }
337
+
338
+ // ============ 排行榜 ============
339
+
340
+ /**
341
+ * 获取积分排行榜
342
+ * @param {Object} params - 查询参数
343
+ * @param {string} params.period - 周期(daily/weekly/monthly/all)
344
+ * @param {number} params.limit - 数量限制
345
+ */
346
+ async getLeaderboard(params = {}) {
347
+ return this.get('/points/leaderboard', params);
348
+ }
349
+
350
+ /**
351
+ * 获取我的排名
352
+ */
353
+ async getMyRank() {
354
+ const params = this._getUserIdentifier();
355
+ return this.get('/points/my/rank', params);
356
+ }
357
+
358
+ // ============ 便捷方法 ============
359
+
360
+ /**
361
+ * 获取可用积分数量
362
+ * @param {string} pointsType - 积分类型
363
+ * @returns {Promise<number>} 可用积分
364
+ */
365
+ async getAvailablePoints(pointsType = null) {
366
+ const result = await this.getBalance(pointsType);
367
+ if (result.code === 0 && result.data) {
368
+ return result.data.available || result.data.balance - (result.data.frozen || 0);
369
+ }
370
+ return 0;
371
+ }
372
+
373
+ /**
374
+ * 检查积分是否充足
375
+ * @param {number} amount - 需要的积分
376
+ * @param {string} pointsType - 积分类型
377
+ * @returns {Promise<{sufficient: boolean, available: number, required: number}>}
378
+ */
379
+ async checkPoints(amount, pointsType = null) {
380
+ const available = await this.getAvailablePoints(pointsType);
381
+ return {
382
+ sufficient: available >= amount,
383
+ available,
384
+ required: amount,
385
+ shortfall: Math.max(0, amount - available)
386
+ };
387
+ }
388
+
389
+ /**
390
+ * 计算积分可抵扣金额
391
+ * @param {number} points - 积分数量
392
+ * @param {number} ratio - 抵扣比例(默认 100 积分 = 1 元)
393
+ * @returns {number} 可抵扣金额
394
+ */
395
+ calculateDeductionAmount(points, ratio = 100) {
396
+ return points / ratio;
397
+ }
398
+ }
399
+
400
+ // 创建默认实例
401
+ const pointsApi = new PointsApiClient();
402
+
403
+ /**
404
+ * Points SDK - 国际化 i18n
405
+ * 积分体系多语言支持
406
+ */
407
+
408
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
409
+ const messages = {
410
+ zh: {
411
+ // 通用
412
+ loading: '加载中...',
413
+ points: '积分',
414
+ credits: '点数',
415
+ balance: '余额',
416
+ available: '可用',
417
+ frozen: '冻结',
418
+ // 积分操作
419
+ earn: '获得',
420
+ spend: '消费',
421
+ transfer: '转赠',
422
+ exchange: '兑换',
423
+ refund: '退款',
424
+ expire: '过期',
425
+ // 积分来源
426
+ sourceTask: '任务奖励',
427
+ sourceLogin: '登录奖励',
428
+ sourceSignIn: '签到奖励',
429
+ sourceInvite: '邀请奖励',
430
+ sourcePurchase: '消费返积分',
431
+ sourceEvent: '活动奖励',
432
+ sourceAdmin: '系统发放',
433
+ sourceTransfer: '好友赠送',
434
+ // 积分消费
435
+ consumeGoods: '兑换商品',
436
+ consumeService: '购买服务',
437
+ consumePrivilege: '解锁权益',
438
+ consumeTransfer: '转赠好友',
439
+ consumeLottery: '抽奖消费',
440
+ // 签到
441
+ checkIn: '签到',
442
+ checkInSuccess: '签到成功',
443
+ checkInReward: '获得 {points} 积分',
444
+ consecutiveDays: '连续签到 {days} 天',
445
+ todayCheckedIn: '今日已签到',
446
+ missedCheckIn: '漏签',
447
+ // 兑换
448
+ exchangePoints: '积分兑换',
449
+ exchangeRate: '兑换比例',
450
+ exchangeHistory: '兑换记录',
451
+ confirmExchange: '确认兑换',
452
+ insufficientPoints: '积分不足',
453
+ // 转赠
454
+ transferPoints: '转赠积分',
455
+ transferTo: '转赠给',
456
+ transferAmount: '转赠数量',
457
+ transferSuccess: '转赠成功',
458
+ transferFailed: '转赠失败',
459
+ // 历史记录
460
+ history: '积分明细',
461
+ income: '收入',
462
+ expense: '支出',
463
+ all: '全部',
464
+ noRecords: '暂无记录',
465
+ // 额外文本
466
+ transferToPlaceholder: '请输入对方 DID',
467
+ transferMinError: '最少转赠 {min} 积分',
468
+ transferSuccessMsg: '已成功转赠 {amount} 积分',
469
+ continueTransfer: '继续转赠',
470
+ minAmountMsg: '最少 {min}',
471
+ allAmount: '全部',
472
+ messageOptional: '留言(可选)',
473
+ messagePlaceholder: '给对方留言...',
474
+ expireWarning: '将于 {date} 过期',
475
+ // 统计
476
+ totalEarned: '累计获得',
477
+ totalSpent: '累计消费',
478
+ thisMonth: '本月获得',
479
+ // 规则
480
+ rules: '积分规则',
481
+ howToEarn: '如何获取积分',
482
+ howToSpend: '如何使用积分',
483
+ expireRule: '积分有效期',
484
+ // 错误
485
+ errorLoad: '加载失败',
486
+ errorTransfer: '转赠失败',
487
+ errorExchange: '兑换失败'
488
+ },
489
+ en: {
490
+ // General
491
+ loading: 'Loading...',
492
+ points: 'Points',
493
+ credits: 'Credits',
494
+ balance: 'Balance',
495
+ available: 'Available',
496
+ frozen: 'Frozen',
497
+ // Point Operations
498
+ earn: 'Earn',
499
+ spend: 'Spend',
500
+ transfer: 'Transfer',
501
+ exchange: 'Exchange',
502
+ refund: 'Refund',
503
+ expire: 'Expire',
504
+ // Point Sources
505
+ sourceTask: 'Task Reward',
506
+ sourceLogin: 'Login Bonus',
507
+ sourceSignIn: 'Check-in Bonus',
508
+ sourceInvite: 'Referral Bonus',
509
+ sourcePurchase: 'Purchase Reward',
510
+ sourceEvent: 'Event Reward',
511
+ sourceAdmin: 'System Grant',
512
+ sourceTransfer: 'Gift Received',
513
+ // Point Consumption
514
+ consumeGoods: 'Redeem Items',
515
+ consumeService: 'Purchase Service',
516
+ consumePrivilege: 'Unlock Privilege',
517
+ consumeTransfer: 'Gift to Friend',
518
+ consumeLottery: 'Lottery Entry',
519
+ // Check-in
520
+ checkIn: 'Check In',
521
+ checkInSuccess: 'Check-in Successful',
522
+ checkInReward: 'Earned {points} points',
523
+ consecutiveDays: '{days} consecutive days',
524
+ todayCheckedIn: 'Already checked in today',
525
+ missedCheckIn: 'Missed',
526
+ // Exchange
527
+ exchangePoints: 'Exchange Points',
528
+ exchangeRate: 'Exchange Rate',
529
+ exchangeHistory: 'Exchange History',
530
+ confirmExchange: 'Confirm Exchange',
531
+ insufficientPoints: 'Insufficient points',
532
+ // Transfer
533
+ transferPoints: 'Transfer Points',
534
+ transferTo: 'Transfer to',
535
+ transferAmount: 'Amount',
536
+ transferSuccess: 'Transfer Successful',
537
+ transferFailed: 'Transfer Failed',
538
+ // History
539
+ history: 'Points History',
540
+ income: 'Income',
541
+ expense: 'Expense',
542
+ all: 'All',
543
+ noRecords: 'No records yet',
544
+ // 额外文本
545
+ transferToPlaceholder: 'Enter recipient DID',
546
+ transferMinError: 'Minimum transfer is {min} points',
547
+ transferSuccessMsg: 'Successfully transferred {amount} points',
548
+ continueTransfer: 'Continue Transfer',
549
+ minAmountMsg: 'Min {min}',
550
+ allAmount: 'All',
551
+ messageOptional: 'Message (Optional)',
552
+ messagePlaceholder: 'Leave a message...',
553
+ expireWarning: 'will expire on {date}',
554
+ // Statistics
555
+ totalEarned: 'Total Earned',
556
+ totalSpent: 'Total Spent',
557
+ thisMonth: 'This Month',
558
+ // Rules
559
+ rules: 'Points Rules',
560
+ howToEarn: 'How to Earn',
561
+ howToSpend: 'How to Spend',
562
+ expireRule: 'Expiration Policy',
563
+ // Errors
564
+ errorLoad: 'Failed to load',
565
+ errorTransfer: 'Transfer failed',
566
+ errorExchange: 'Exchange failed'
567
+ },
568
+ ja: {
569
+ // General
570
+ loading: '読み込み中...',
571
+ points: 'ポイント',
572
+ credits: 'クレジット',
573
+ balance: '残高',
574
+ available: '利用可能',
575
+ frozen: '凍結中',
576
+ // Point Operations
577
+ earn: '獲得',
578
+ spend: '消費',
579
+ transfer: '譲渡',
580
+ exchange: '交換',
581
+ refund: '返金',
582
+ expire: '失効',
583
+ // Point Sources
584
+ sourceTask: 'タスク報酬',
585
+ sourceLogin: 'ログインボーナス',
586
+ sourceSignIn: 'チェックイン報酬',
587
+ sourceInvite: '紹介ボーナス',
588
+ sourcePurchase: '購入還元',
589
+ sourceEvent: 'イベント報酬',
590
+ sourceAdmin: 'システム付与',
591
+ sourceTransfer: 'ギフト受け取り',
592
+ // Point Consumption
593
+ consumeGoods: 'アイテム交換',
594
+ consumeService: 'サービス購入',
595
+ consumePrivilege: '特典ロック解除',
596
+ consumeTransfer: '友達にギフト',
597
+ consumeLottery: '抽選参加',
598
+ // Check-in
599
+ checkIn: 'チェックイン',
600
+ checkInSuccess: 'チェックイン成功',
601
+ checkInReward: '{points} ポイント獲得',
602
+ consecutiveDays: '{days} 日連続',
603
+ todayCheckedIn: '本日はチェックイン済みです',
604
+ missedCheckIn: 'チェックイン忘れ',
605
+ // Exchange
606
+ exchangePoints: 'ポイント交換',
607
+ exchangeRate: '交換レート',
608
+ exchangeHistory: '交換履歴',
609
+ confirmExchange: '交換を確認',
610
+ insufficientPoints: 'ポイントが不足しています',
611
+ // Transfer
612
+ transferPoints: 'ポイントを譲渡',
613
+ transferTo: '譲渡先',
614
+ transferAmount: '数量',
615
+ transferSuccess: '譲渡成功',
616
+ transferFailed: '譲渡失敗',
617
+ // History
618
+ history: 'ポイント履歴',
619
+ income: '収入',
620
+ expense: '支出',
621
+ all: 'すべて',
622
+ noRecords: '記録がありません',
623
+ // 额外文本
624
+ transferToPlaceholder: '相手のDIDを入力',
625
+ transferMinError: '最小 {min} ポイントから譲渡可能',
626
+ transferSuccessMsg: '{amount} ポイントを譲渡しました',
627
+ continueTransfer: '続けて譲渡',
628
+ minAmountMsg: '最小 {min}',
629
+ allAmount: 'すべて',
630
+ messageOptional: 'メッセージ(任意)',
631
+ messagePlaceholder: 'メッセージを残す...',
632
+ expireWarning: '{date} に失効します',
633
+ // Statistics
634
+ totalEarned: '累計獲得',
635
+ totalSpent: '累計消費',
636
+ thisMonth: '今月',
637
+ // Rules
638
+ rules: 'ポイントルール',
639
+ howToEarn: '獲得方法',
640
+ howToSpend: '利用方法',
641
+ expireRule: '有効期限について',
642
+ // Errors
643
+ errorLoad: '読み込み失敗',
644
+ errorTransfer: '譲渡失敗',
645
+ errorExchange: '交換失敗'
646
+ },
647
+ ko: {
648
+ // General
649
+ loading: '로딩 중...',
650
+ points: '포인트',
651
+ credits: '크레딧',
652
+ balance: '잔액',
653
+ available: '사용 가능',
654
+ frozen: '동결됨',
655
+ // Point Operations
656
+ earn: '획득',
657
+ spend: '사용',
658
+ transfer: '양도',
659
+ exchange: '교환',
660
+ refund: '환불',
661
+ expire: '만료',
662
+ // Point Sources
663
+ sourceTask: '미션 보상',
664
+ sourceLogin: '로그인 보너스',
665
+ sourceSignIn: '출석 체크 보상',
666
+ sourceInvite: '초대 보너스',
667
+ sourcePurchase: '구매 리워드',
668
+ sourceEvent: '이벤트 보상',
669
+ sourceAdmin: '시스템 지급',
670
+ sourceTransfer: '선물 받음',
671
+ // Point Consumption
672
+ consumeGoods: '아이템 교환',
673
+ consumeService: '서비스 구매',
674
+ consumePrivilege: '혜택 잠금 해제',
675
+ consumeTransfer: '친구에게 선물',
676
+ consumeLottery: '추첨 참여',
677
+ // Check-in
678
+ checkIn: '출석 체크',
679
+ checkInSuccess: '출석 체크 완료',
680
+ checkInReward: '{points} 포인트 획득',
681
+ consecutiveDays: '{days}일 연속',
682
+ todayCheckedIn: '오늘 이미 출석했습니다',
683
+ missedCheckIn: '출석 누락',
684
+ // Exchange
685
+ exchangePoints: '포인트 교환',
686
+ exchangeRate: '교환 비율',
687
+ exchangeHistory: '교환 내역',
688
+ confirmExchange: '교환 확인',
689
+ insufficientPoints: '포인트 부족',
690
+ // Transfer
691
+ transferPoints: '포인트 양도',
692
+ transferTo: '양도 대상',
693
+ transferAmount: '수량',
694
+ transferSuccess: '양도 성공',
695
+ transferFailed: '양도 실패',
696
+ // History
697
+ history: '포인트 내역',
698
+ income: '수입',
699
+ expense: '지출',
700
+ all: '전체',
701
+ noRecords: '기록이 없습니다',
702
+ // 额外文本
703
+ transferToPlaceholder: '상대방의 DID 입력',
704
+ transferMinError: '최소 {min} 포인트부터 양도 가능',
705
+ transferSuccessMsg: '{amount} 포인트를 양도했습니다',
706
+ continueTransfer: '계속 양도하기',
707
+ minAmountMsg: '최소 {min}',
708
+ allAmount: '전체',
709
+ messageOptional: '메시지 (선택)',
710
+ messagePlaceholder: '메시지 남기기...',
711
+ expireWarning: '{date} 에 만료됩니다',
712
+ // Statistics
713
+ totalEarned: '누적 획득',
714
+ totalSpent: '누적 사용',
715
+ thisMonth: '이번 달',
716
+ // Rules
717
+ rules: '포인트 규칙',
718
+ howToEarn: '획득 방법',
719
+ howToSpend: '사용 방법',
720
+ expireRule: '만료 정책',
721
+ // Errors
722
+ errorLoad: '로딩 실패',
723
+ errorTransfer: '양도 실패',
724
+ errorExchange: '교환 실패'
725
+ }
726
+ };
727
+ let currentLanguage = 'zh';
728
+ function setLanguage(lang) {
729
+ if (SUPPORTED_LANGUAGES.includes(lang)) {
730
+ currentLanguage = lang;
731
+ if (typeof window !== 'undefined') {
732
+ localStorage.setItem('qbit_did_points_language', lang);
733
+ }
734
+ }
735
+ }
736
+ function getLanguage() {
737
+ if (typeof window !== 'undefined') {
738
+ const saved = localStorage.getItem('qbit_did_points_language');
739
+ if (saved && SUPPORTED_LANGUAGES.includes(saved)) {
740
+ currentLanguage = saved;
741
+ }
742
+ }
743
+ return currentLanguage;
744
+ }
745
+ function t(key, params) {
746
+ const msgs = messages[getLanguage()] || messages.zh;
747
+ let text = msgs[key] || key;
748
+ if (params) {
749
+ Object.keys(params).forEach(k => {
750
+ text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), params[k]);
751
+ });
752
+ }
753
+ return text;
754
+ }
755
+
756
+ /**
757
+ * Points SDK - Context Provider
758
+ * 积分体系状态管理
759
+ */
760
+
761
+ const PointsContext = /*#__PURE__*/React.createContext(null);
762
+
763
+ /**
764
+ * Points Provider 组件
765
+ */
766
+ function PointsProvider({
767
+ children,
768
+ apiUrl,
769
+ token,
770
+ language = 'zh',
771
+ onPointsChange,
772
+ onError
773
+ }) {
774
+ const [account, setAccount] = React.useState(null);
775
+ const [checkInStatus, setCheckInStatus] = React.useState(null);
776
+ const [loading, setLoading] = React.useState(false);
777
+ const [error, setError] = React.useState(null);
778
+
779
+ // 初始化配置
780
+ React.useEffect(() => {
781
+ if (apiUrl) pointsApi.setBaseUrl(apiUrl);
782
+ if (token) pointsApi.setToken(token);
783
+ if (language) setLanguage(language);
784
+ }, [apiUrl, token, language]);
785
+ const handleError = React.useCallback(err => {
786
+ setError(err.message);
787
+ onError?.(err);
788
+ }, [onError]);
789
+
790
+ // ============ 账户操作 ============
791
+
792
+ /** 加载积分账户 */
793
+ const loadAccount = React.useCallback(async () => {
794
+ setLoading(true);
795
+ setError(null);
796
+ try {
797
+ const result = await pointsApi.getMyAccount();
798
+ const prevBalance = account?.balance;
799
+ setAccount(result);
800
+ // 检测积分变化
801
+ if (prevBalance !== undefined && result.balance !== prevBalance) {
802
+ onPointsChange?.(result.balance - prevBalance, result);
803
+ }
804
+ return result;
805
+ } catch (err) {
806
+ handleError(err);
807
+ throw err;
808
+ } finally {
809
+ setLoading(false);
810
+ }
811
+ }, [account, onPointsChange, handleError]);
812
+
813
+ /** 刷新账户 */
814
+ const refreshAccount = React.useCallback(() => loadAccount(), [loadAccount]);
815
+
816
+ // ============ 签到操作 ============
817
+
818
+ /** 加载签到状态 */
819
+ const loadCheckInStatus = React.useCallback(async () => {
820
+ try {
821
+ const result = await pointsApi.getCheckInStatus();
822
+ setCheckInStatus(result);
823
+ return result;
824
+ } catch (err) {
825
+ handleError(err);
826
+ throw err;
827
+ }
828
+ }, [handleError]);
829
+
830
+ /** 执行签到 */
831
+ const checkIn = React.useCallback(async () => {
832
+ setLoading(true);
833
+ try {
834
+ const result = await pointsApi.checkIn();
835
+ setCheckInStatus(prev => ({
836
+ ...prev,
837
+ checked_in_today: true
838
+ }));
839
+ // 刷新账户余额
840
+ await loadAccount();
841
+ return result;
842
+ } catch (err) {
843
+ handleError(err);
844
+ throw err;
845
+ } finally {
846
+ setLoading(false);
847
+ }
848
+ }, [loadAccount, handleError]);
849
+
850
+ // ============ 消费操作 ============
851
+
852
+ /** 消费积分 */
853
+ const spendPoints = React.useCallback(async (amount, reason, options = {}) => {
854
+ setLoading(true);
855
+ try {
856
+ const result = await pointsApi.spendPoints({
857
+ amount,
858
+ reason,
859
+ ...options
860
+ });
861
+ await loadAccount();
862
+ return result;
863
+ } catch (err) {
864
+ handleError(err);
865
+ throw err;
866
+ } finally {
867
+ setLoading(false);
868
+ }
869
+ }, [loadAccount, handleError]);
870
+
871
+ // ============ 转赠操作 ============
872
+
873
+ /** 转赠积分 */
874
+ const transferPoints = React.useCallback(async (toDid, amount, message = '') => {
875
+ setLoading(true);
876
+ try {
877
+ const result = await pointsApi.transferPoints({
878
+ to_did: toDid,
879
+ amount,
880
+ message
881
+ });
882
+ await loadAccount();
883
+ return result;
884
+ } catch (err) {
885
+ handleError(err);
886
+ throw err;
887
+ } finally {
888
+ setLoading(false);
889
+ }
890
+ }, [loadAccount, handleError]);
891
+
892
+ // ============ 兑换操作 ============
893
+
894
+ /** 兑换积分 */
895
+ const exchangePoints = React.useCallback(async (ruleId, amount) => {
896
+ setLoading(true);
897
+ try {
898
+ const result = await pointsApi.exchangePoints({
899
+ rule_id: ruleId,
900
+ amount
901
+ });
902
+ await loadAccount();
903
+ return result;
904
+ } catch (err) {
905
+ handleError(err);
906
+ throw err;
907
+ } finally {
908
+ setLoading(false);
909
+ }
910
+ }, [loadAccount, handleError]);
911
+
912
+ // ============ 初始化 ============
913
+
914
+ const initialize = React.useCallback(async () => {
915
+ setLoading(true);
916
+ try {
917
+ await Promise.all([loadAccount(), loadCheckInStatus()]);
918
+ } catch (err) {
919
+ // 错误已在各自 load 函数中处理
920
+ } finally {
921
+ setLoading(false);
922
+ }
923
+ }, [loadAccount, loadCheckInStatus]);
924
+ const value = {
925
+ // 状态
926
+ account,
927
+ checkInStatus,
928
+ loading,
929
+ error,
930
+ // 账户操作
931
+ loadAccount,
932
+ refreshAccount,
933
+ // 签到操作
934
+ loadCheckInStatus,
935
+ checkIn,
936
+ // 积分操作
937
+ spendPoints,
938
+ transferPoints,
939
+ exchangePoints,
940
+ // 初始化
941
+ initialize,
942
+ // API 实例
943
+ api: pointsApi
944
+ };
945
+ return /*#__PURE__*/React.createElement(PointsContext.Provider, {
946
+ value: value
947
+ }, children);
948
+ }
949
+
950
+ /**
951
+ * 使用 Points Context
952
+ */
953
+ function usePoints() {
954
+ const context = React.useContext(PointsContext);
955
+ if (!context) {
956
+ throw new Error('usePoints must be used within a PointsProvider');
957
+ }
958
+ return context;
959
+ }
960
+
961
+ /**
962
+ * Points SDK - 类型定义
963
+ * 积分体系的核心类型
964
+ */
965
+
966
+ // 积分交易类型
967
+ const TransactionType = {
968
+ EARN: 'earn',
969
+ // 获得
970
+ SPEND: 'spend',
971
+ // 消费
972
+ TRANSFER_IN: 'transfer_in',
973
+ // 转入
974
+ TRANSFER_OUT: 'transfer_out',
975
+ // 转出
976
+ REFUND: 'refund',
977
+ // 退款
978
+ EXPIRE: 'expire',
979
+ // 过期
980
+ FREEZE: 'freeze',
981
+ // 冻结
982
+ UNFREEZE: 'unfreeze' // 解冻
983
+ };
984
+
985
+ // 积分来源类型
986
+ const PointSource = {
987
+ TASK: 'task',
988
+ // 任务奖励
989
+ LOGIN: 'login',
990
+ // 登录奖励
991
+ SIGN_IN: 'sign_in',
992
+ // 签到奖励
993
+ INVITE: 'invite',
994
+ // 邀请奖励
995
+ PURCHASE: 'purchase',
996
+ // 消费返积分
997
+ EVENT: 'event',
998
+ // 活动奖励
999
+ ADMIN: 'admin',
1000
+ // 系统发放
1001
+ TRANSFER: 'transfer',
1002
+ // 好友赠送
1003
+ LOTTERY: 'lottery' // 抽奖获得
1004
+ };
1005
+
1006
+ // 积分消费类型
1007
+ const PointConsume = {
1008
+ GOODS: 'goods',
1009
+ // 兑换商品
1010
+ SERVICE: 'service',
1011
+ // 购买服务
1012
+ PRIVILEGE: 'privilege',
1013
+ // 解锁权益
1014
+ TRANSFER: 'transfer',
1015
+ // 转赠好友
1016
+ LOTTERY: 'lottery',
1017
+ // 抽奖消费
1018
+ ORDER: 'order' // 订单抵扣
1019
+ };
1020
+
1021
+ // 积分状态
1022
+ const PointStatus = {
1023
+ AVAILABLE: 'available',
1024
+ // 可用
1025
+ FROZEN: 'frozen',
1026
+ // 冻结
1027
+ EXPIRED: 'expired',
1028
+ // 已过期
1029
+ USED: 'used' // 已使用
1030
+ };
1031
+
1032
+ /**
1033
+ * 创建默认积分账户
1034
+ */
1035
+ function createDefaultPointsAccount(overrides = {}) {
1036
+ return {
1037
+ user_did: null,
1038
+ balance: 0,
1039
+ available: 0,
1040
+ frozen: 0,
1041
+ total_earned: 0,
1042
+ total_spent: 0,
1043
+ this_month_earned: 0,
1044
+ will_expire: 0,
1045
+ expire_date: null,
1046
+ created_at: null,
1047
+ updated_at: null,
1048
+ ...overrides
1049
+ };
1050
+ }
1051
+
1052
+ /**
1053
+ * 创建默认积分交易记录
1054
+ */
1055
+ function createDefaultPointsTransaction(overrides = {}) {
1056
+ return {
1057
+ id: null,
1058
+ user_did: null,
1059
+ type: TransactionType.EARN,
1060
+ amount: 0,
1061
+ balance_after: 0,
1062
+ source: null,
1063
+ source_id: null,
1064
+ description: '',
1065
+ expire_at: null,
1066
+ created_at: null,
1067
+ ...overrides
1068
+ };
1069
+ }
1070
+
1071
+ /**
1072
+ * 创建默认签到配置
1073
+ */
1074
+ function createDefaultCheckInConfig(overrides = {}) {
1075
+ return {
1076
+ base_points: 10,
1077
+ max_consecutive_bonus: 7,
1078
+ consecutive_bonus: [10, 10, 15, 15, 20, 20, 30],
1079
+ // 7天连续签到奖励
1080
+ monthly_special_days: [],
1081
+ // 特殊日期额外奖励
1082
+ special_day_bonus: 50,
1083
+ ...overrides
1084
+ };
1085
+ }
1086
+
1087
+ /**
1088
+ * 创建默认兑换规则
1089
+ */
1090
+ function createDefaultExchangeRule(overrides = {}) {
1091
+ return {
1092
+ id: null,
1093
+ name: '',
1094
+ from_type: 'points',
1095
+ to_type: 'credits',
1096
+ rate: 100,
1097
+ // 100 积分 = 1 点数
1098
+ min_amount: 100,
1099
+ max_amount: 10000,
1100
+ daily_limit: 1,
1101
+ is_active: true,
1102
+ ...overrides
1103
+ };
1104
+ }
1105
+
1106
+ /**
1107
+ * 格式化积分数值
1108
+ */
1109
+ function formatPoints(value, options = {}) {
1110
+ const {
1111
+ compact = false,
1112
+ prefix = '',
1113
+ suffix = ''
1114
+ } = options;
1115
+ if (compact) {
1116
+ if (value >= 10000) {
1117
+ return `${prefix}${(value / 10000).toFixed(1)}w${suffix}`;
1118
+ }
1119
+ if (value >= 1000) {
1120
+ return `${prefix}${(value / 1000).toFixed(1)}k${suffix}`;
1121
+ }
1122
+ }
1123
+ return `${prefix}${value.toLocaleString()}${suffix}`;
1124
+ }
1125
+
1126
+ /**
1127
+ * Points SDK - PointsBalance 组件
1128
+ * 积分余额展示
1129
+ */
1130
+
1131
+
1132
+ /**
1133
+ * 积分余额组件
1134
+ */
1135
+ function PointsBalance({
1136
+ balance = 0,
1137
+ available = 0,
1138
+ frozen = 0,
1139
+ willExpire = 0,
1140
+ expireDate = null,
1141
+ size = 'medium',
1142
+ // small, medium, large
1143
+ showDetails = true,
1144
+ showExpire = true,
1145
+ onRefresh,
1146
+ className = ''
1147
+ }) {
1148
+ const sizeClasses = {
1149
+ small: 'points-balance-sm',
1150
+ medium: 'points-balance-md',
1151
+ large: 'points-balance-lg'
1152
+ };
1153
+ const formatDate = dateStr => {
1154
+ if (!dateStr) return '';
1155
+ return new Date(dateStr).toLocaleDateString();
1156
+ };
1157
+ return /*#__PURE__*/React.createElement("div", {
1158
+ className: `points-balance ${sizeClasses[size]} ${className}`
1159
+ }, /*#__PURE__*/React.createElement("div", {
1160
+ className: "points-balance-main"
1161
+ }, /*#__PURE__*/React.createElement("div", {
1162
+ className: "points-balance-icon"
1163
+ }, "\uD83D\uDCB0"), /*#__PURE__*/React.createElement("div", {
1164
+ className: "points-balance-info"
1165
+ }, /*#__PURE__*/React.createElement("span", {
1166
+ className: "points-balance-label"
1167
+ }, t('balance')), /*#__PURE__*/React.createElement("span", {
1168
+ className: "points-balance-value"
1169
+ }, formatPoints(balance))), onRefresh && /*#__PURE__*/React.createElement("button", {
1170
+ className: "points-refresh-btn",
1171
+ onClick: onRefresh
1172
+ }, "\uD83D\uDD04")), showDetails && /*#__PURE__*/React.createElement("div", {
1173
+ className: "points-balance-details"
1174
+ }, /*#__PURE__*/React.createElement("div", {
1175
+ className: "points-detail-item"
1176
+ }, /*#__PURE__*/React.createElement("span", {
1177
+ className: "points-detail-label"
1178
+ }, t('available')), /*#__PURE__*/React.createElement("span", {
1179
+ className: "points-detail-value available"
1180
+ }, formatPoints(available))), frozen > 0 && /*#__PURE__*/React.createElement("div", {
1181
+ className: "points-detail-item"
1182
+ }, /*#__PURE__*/React.createElement("span", {
1183
+ className: "points-detail-label"
1184
+ }, t('frozen')), /*#__PURE__*/React.createElement("span", {
1185
+ className: "points-detail-value frozen"
1186
+ }, formatPoints(frozen)))), showExpire && willExpire > 0 && /*#__PURE__*/React.createElement("div", {
1187
+ className: "points-expire-warning"
1188
+ }, /*#__PURE__*/React.createElement("span", {
1189
+ className: "points-expire-icon"
1190
+ }, "\u26A0\uFE0F"), /*#__PURE__*/React.createElement("span", null, formatPoints(willExpire), " ", t('points'), expireDate && ` ${t('expireWarning', {
1191
+ date: formatDate(expireDate)
1192
+ })}`)));
1193
+ }
1194
+
1195
+ /**
1196
+ * Points SDK - CheckInCalendar 组件
1197
+ * 签到日历
1198
+ */
1199
+
1200
+
1201
+ /**
1202
+ * 签到日历组件
1203
+ */
1204
+ function CheckInCalendar({
1205
+ onCheckIn,
1206
+ className = ''
1207
+ }) {
1208
+ const {
1209
+ api,
1210
+ checkInStatus,
1211
+ checkIn: doCheckIn,
1212
+ loading
1213
+ } = usePoints();
1214
+ const [calendar, setCalendar] = React.useState([]);
1215
+ const [currentMonth, setCurrentMonth] = React.useState(() => {
1216
+ const now = new Date();
1217
+ return {
1218
+ year: now.getFullYear(),
1219
+ month: now.getMonth() + 1
1220
+ };
1221
+ });
1222
+
1223
+ // 加载签到日历
1224
+ const loadCalendar = React.useCallback(async () => {
1225
+ try {
1226
+ const result = await api.getCheckInCalendar(currentMonth.year, currentMonth.month);
1227
+ setCalendar(result.days || []);
1228
+ } catch (err) {
1229
+ console.error('Load calendar error:', err);
1230
+ }
1231
+ }, [api, currentMonth]);
1232
+ React.useEffect(() => {
1233
+ loadCalendar();
1234
+ }, [loadCalendar]);
1235
+
1236
+ // 生成日历网格
1237
+ const generateCalendarGrid = React.useCallback(() => {
1238
+ const {
1239
+ year,
1240
+ month
1241
+ } = currentMonth;
1242
+ const firstDay = new Date(year, month - 1, 1).getDay();
1243
+ const daysInMonth = new Date(year, month, 0).getDate();
1244
+ const today = new Date();
1245
+ const isCurrentMonth = today.getFullYear() === year && today.getMonth() + 1 === month;
1246
+ const grid = [];
1247
+
1248
+ // 空白格子
1249
+ for (let i = 0; i < firstDay; i++) {
1250
+ grid.push({
1251
+ day: null,
1252
+ status: 'empty'
1253
+ });
1254
+ }
1255
+
1256
+ // 日期格子
1257
+ for (let day = 1; day <= daysInMonth; day++) {
1258
+ const checkedIn = calendar.includes(day);
1259
+ const isFuture = isCurrentMonth && day > today.getDate();
1260
+ const isToday = isCurrentMonth && day === today.getDate();
1261
+ grid.push({
1262
+ day,
1263
+ status: isFuture ? 'future' : checkedIn ? 'checked' : 'missed',
1264
+ isToday
1265
+ });
1266
+ }
1267
+ return grid;
1268
+ }, [currentMonth, calendar]);
1269
+
1270
+ // 切换月份
1271
+ const changeMonth = delta => {
1272
+ setCurrentMonth(prev => {
1273
+ let newMonth = prev.month + delta;
1274
+ let newYear = prev.year;
1275
+ if (newMonth < 1) {
1276
+ newMonth = 12;
1277
+ newYear--;
1278
+ } else if (newMonth > 12) {
1279
+ newMonth = 1;
1280
+ newYear++;
1281
+ }
1282
+ return {
1283
+ year: newYear,
1284
+ month: newMonth
1285
+ };
1286
+ });
1287
+ };
1288
+
1289
+ // 执行签到
1290
+ const handleCheckIn = async () => {
1291
+ try {
1292
+ const result = await doCheckIn();
1293
+ await loadCalendar();
1294
+ onCheckIn?.(result);
1295
+ } catch (err) {
1296
+ console.error('Check-in error:', err);
1297
+ }
1298
+ };
1299
+ const grid = generateCalendarGrid();
1300
+ const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
1301
+ const canCheckIn = !checkInStatus?.checked_in_today;
1302
+ return /*#__PURE__*/React.createElement("div", {
1303
+ className: `points-check-in ${className}`
1304
+ }, /*#__PURE__*/React.createElement("div", {
1305
+ className: "points-check-in-header"
1306
+ }, /*#__PURE__*/React.createElement("button", {
1307
+ className: "points-month-btn",
1308
+ onClick: () => changeMonth(-1)
1309
+ }, "\u2039"), /*#__PURE__*/React.createElement("span", {
1310
+ className: "points-month-text"
1311
+ }, currentMonth.year, " \u5E74 ", currentMonth.month, " \u6708"), /*#__PURE__*/React.createElement("button", {
1312
+ className: "points-month-btn",
1313
+ onClick: () => changeMonth(1)
1314
+ }, "\u203A")), checkInStatus && /*#__PURE__*/React.createElement("div", {
1315
+ className: "points-streak-info"
1316
+ }, /*#__PURE__*/React.createElement("span", {
1317
+ className: "points-streak-badge"
1318
+ }, "\uD83D\uDD25 ", t('consecutiveDays', {
1319
+ days: checkInStatus.consecutive_days || 0
1320
+ })), checkInStatus.next_reward && /*#__PURE__*/React.createElement("span", {
1321
+ className: "points-next-reward"
1322
+ }, "\u660E\u65E5\u5956\u52B1: +", checkInStatus.next_reward)), /*#__PURE__*/React.createElement("div", {
1323
+ className: "points-calendar"
1324
+ }, /*#__PURE__*/React.createElement("div", {
1325
+ className: "points-calendar-header"
1326
+ }, weekDays.map(day => /*#__PURE__*/React.createElement("span", {
1327
+ key: day,
1328
+ className: "points-week-day"
1329
+ }, day))), /*#__PURE__*/React.createElement("div", {
1330
+ className: "points-calendar-grid"
1331
+ }, grid.map((cell, index) => /*#__PURE__*/React.createElement("div", {
1332
+ key: index,
1333
+ className: `points-calendar-day ${cell.status} ${cell.isToday ? 'today' : ''}`
1334
+ }, cell.day && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
1335
+ className: "points-day-number"
1336
+ }, cell.day), cell.status === 'checked' && /*#__PURE__*/React.createElement("span", {
1337
+ className: "points-check-mark"
1338
+ }, "\u2713")))))), /*#__PURE__*/React.createElement("button", {
1339
+ className: `points-check-in-btn ${canCheckIn ? '' : 'checked'}`,
1340
+ onClick: handleCheckIn,
1341
+ disabled: !canCheckIn || loading
1342
+ }, loading ? t('loading') : canCheckIn ? t('checkIn') : t('todayCheckedIn')));
1343
+ }
1344
+
1345
+ /**
1346
+ * Points SDK - PointsHistory 组件
1347
+ * 积分历史记录
1348
+ */
1349
+
1350
+
1351
+ /**
1352
+ * 积分历史组件
1353
+ */
1354
+ function PointsHistory$1({
1355
+ pageSize = 20,
1356
+ showFilter = true,
1357
+ className = ''
1358
+ }) {
1359
+ const {
1360
+ api
1361
+ } = usePoints();
1362
+ const [history, setHistory] = React.useState([]);
1363
+ const [loading, setLoading] = React.useState(false);
1364
+ const [filter, setFilter] = React.useState('all'); // all, income, expense
1365
+ const [pagination, setPagination] = React.useState({
1366
+ page: 1,
1367
+ total: 0,
1368
+ totalPages: 0
1369
+ });
1370
+
1371
+ // 加载历史记录
1372
+ const loadHistory = React.useCallback(async (page = 1) => {
1373
+ setLoading(true);
1374
+ try {
1375
+ const params = {
1376
+ page,
1377
+ page_size: pageSize
1378
+ };
1379
+ if (filter === 'income') {
1380
+ params.type = [TransactionType.EARN, TransactionType.TRANSFER_IN, TransactionType.REFUND].join(',');
1381
+ } else if (filter === 'expense') {
1382
+ params.type = [TransactionType.SPEND, TransactionType.TRANSFER_OUT, TransactionType.EXPIRE].join(',');
1383
+ }
1384
+ const result = await api.getHistory(params);
1385
+ const items = result.items || result.data || [];
1386
+ setHistory(items);
1387
+ setPagination({
1388
+ page,
1389
+ total: result.total || items.length,
1390
+ totalPages: result.total_pages || Math.ceil((result.total || items.length) / pageSize)
1391
+ });
1392
+ } catch (err) {
1393
+ console.error('Load history error:', err);
1394
+ } finally {
1395
+ setLoading(false);
1396
+ }
1397
+ }, [api, filter, pageSize]);
1398
+ React.useEffect(() => {
1399
+ loadHistory(1);
1400
+ }, [filter]);
1401
+
1402
+ // 获取交易类型样式
1403
+ const getTypeStyle = type => {
1404
+ const incomeTypes = [TransactionType.EARN, TransactionType.TRANSFER_IN, TransactionType.REFUND];
1405
+ return incomeTypes.includes(type) ? 'income' : 'expense';
1406
+ };
1407
+
1408
+ // 格式化日期
1409
+ const formatDate = dateStr => {
1410
+ if (!dateStr) return '';
1411
+ const date = new Date(dateStr);
1412
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {
1413
+ hour: '2-digit',
1414
+ minute: '2-digit'
1415
+ });
1416
+ };
1417
+ return /*#__PURE__*/React.createElement("div", {
1418
+ className: `points-history ${className}`
1419
+ }, showFilter && /*#__PURE__*/React.createElement("div", {
1420
+ className: "points-history-filter"
1421
+ }, /*#__PURE__*/React.createElement("button", {
1422
+ className: filter === 'all' ? 'active' : '',
1423
+ onClick: () => setFilter('all')
1424
+ }, t('all')), /*#__PURE__*/React.createElement("button", {
1425
+ className: filter === 'income' ? 'active' : '',
1426
+ onClick: () => setFilter('income')
1427
+ }, t('income')), /*#__PURE__*/React.createElement("button", {
1428
+ className: filter === 'expense' ? 'active' : '',
1429
+ onClick: () => setFilter('expense')
1430
+ }, t('expense'))), loading ? /*#__PURE__*/React.createElement("div", {
1431
+ className: "points-history-loading"
1432
+ }, /*#__PURE__*/React.createElement("div", {
1433
+ className: "points-loading-spinner"
1434
+ }), /*#__PURE__*/React.createElement("span", null, t('loading'))) : history.length === 0 ? /*#__PURE__*/React.createElement("div", {
1435
+ className: "points-history-empty"
1436
+ }, /*#__PURE__*/React.createElement("span", {
1437
+ className: "points-empty-icon"
1438
+ }, "\uD83D\uDCCB"), /*#__PURE__*/React.createElement("span", null, t('noRecords'))) : /*#__PURE__*/React.createElement("div", {
1439
+ className: "points-history-list"
1440
+ }, history.map((item, index) => /*#__PURE__*/React.createElement("div", {
1441
+ key: item.id || index,
1442
+ className: "points-history-item"
1443
+ }, /*#__PURE__*/React.createElement("div", {
1444
+ className: "points-history-icon"
1445
+ }, getTypeStyle(item.type) === 'income' ? '📈' : '📉'), /*#__PURE__*/React.createElement("div", {
1446
+ className: "points-history-info"
1447
+ }, /*#__PURE__*/React.createElement("span", {
1448
+ className: "points-history-desc"
1449
+ }, item.description), /*#__PURE__*/React.createElement("span", {
1450
+ className: "points-history-time"
1451
+ }, formatDate(item.created_at))), /*#__PURE__*/React.createElement("div", {
1452
+ className: `points-history-amount ${getTypeStyle(item.type)}`
1453
+ }, getTypeStyle(item.type) === 'income' ? '+' : '-', formatPoints(Math.abs(item.amount)))))), pagination.totalPages > 1 && /*#__PURE__*/React.createElement("div", {
1454
+ className: "points-history-pagination"
1455
+ }, /*#__PURE__*/React.createElement("button", {
1456
+ disabled: pagination.page <= 1,
1457
+ onClick: () => loadHistory(pagination.page - 1)
1458
+ }, "\u4E0A\u4E00\u9875"), /*#__PURE__*/React.createElement("span", null, pagination.page, " / ", pagination.totalPages), /*#__PURE__*/React.createElement("button", {
1459
+ disabled: pagination.page >= pagination.totalPages,
1460
+ onClick: () => loadHistory(pagination.page + 1)
1461
+ }, "\u4E0B\u4E00\u9875")));
1462
+ }
1463
+
1464
+ /**
1465
+ * Points SDK - TransferPoints 组件
1466
+ * 积分转赠
1467
+ */
1468
+
1469
+
1470
+ /**
1471
+ * 积分转赠组件
1472
+ */
1473
+ function TransferPoints({
1474
+ maxAmount = 0,
1475
+ minAmount = 1,
1476
+ onSuccess,
1477
+ onCancel,
1478
+ className = ''
1479
+ }) {
1480
+ const {
1481
+ account,
1482
+ transferPoints,
1483
+ loading
1484
+ } = usePoints();
1485
+ const [toDid, setToDid] = React.useState('');
1486
+ const [amount, setAmount] = React.useState('');
1487
+ const [message, setMessage] = React.useState('');
1488
+ const [error, setError] = React.useState('');
1489
+ const [success, setSuccess] = React.useState(false);
1490
+ const available = maxAmount || account?.available || 0;
1491
+
1492
+ // 验证
1493
+ const validate = () => {
1494
+ if (!toDid.trim()) {
1495
+ setError(t('transferToPlaceholder'));
1496
+ return false;
1497
+ }
1498
+ const amt = parseInt(amount);
1499
+ if (!amt || amt < minAmount) {
1500
+ setError(t('transferMinError', {
1501
+ min: minAmount
1502
+ }));
1503
+ return false;
1504
+ }
1505
+ if (amt > available) {
1506
+ setError(t('insufficientPoints'));
1507
+ return false;
1508
+ }
1509
+ return true;
1510
+ };
1511
+
1512
+ // 提交转赠
1513
+ const handleSubmit = async () => {
1514
+ setError('');
1515
+ if (!validate()) return;
1516
+ try {
1517
+ const result = await transferPoints(toDid, parseInt(amount), message);
1518
+ setSuccess(true);
1519
+ onSuccess?.(result);
1520
+ } catch (err) {
1521
+ setError(err.message || t('transferFailed'));
1522
+ }
1523
+ };
1524
+
1525
+ // 设置快捷金额
1526
+ const setQuickAmount = percent => {
1527
+ const amt = Math.floor(available * percent / 100);
1528
+ setAmount(amt.toString());
1529
+ };
1530
+ if (success) {
1531
+ return /*#__PURE__*/React.createElement("div", {
1532
+ className: `points-transfer points-transfer-success ${className}`
1533
+ }, /*#__PURE__*/React.createElement("div", {
1534
+ className: "points-success-icon"
1535
+ }, "\u2705"), /*#__PURE__*/React.createElement("h3", null, t('transferSuccess')), /*#__PURE__*/React.createElement("p", null, t('transferSuccessMsg', {
1536
+ amount: formatPoints(parseInt(amount))
1537
+ })), /*#__PURE__*/React.createElement("button", {
1538
+ className: "points-btn points-btn-primary",
1539
+ onClick: () => {
1540
+ setSuccess(false);
1541
+ setToDid('');
1542
+ setAmount('');
1543
+ setMessage('');
1544
+ }
1545
+ }, t('continueTransfer')));
1546
+ }
1547
+ return /*#__PURE__*/React.createElement("div", {
1548
+ className: `points-transfer ${className}`
1549
+ }, /*#__PURE__*/React.createElement("h3", null, t('transferPoints')), /*#__PURE__*/React.createElement("div", {
1550
+ className: "points-transfer-balance"
1551
+ }, /*#__PURE__*/React.createElement("span", null, t('available'), ":"), /*#__PURE__*/React.createElement("span", {
1552
+ className: "points-balance-value"
1553
+ }, formatPoints(available))), /*#__PURE__*/React.createElement("div", {
1554
+ className: "points-transfer-form"
1555
+ }, /*#__PURE__*/React.createElement("div", {
1556
+ className: "points-form-group"
1557
+ }, /*#__PURE__*/React.createElement("label", null, t('transferTo')), /*#__PURE__*/React.createElement("input", {
1558
+ type: "text",
1559
+ className: "points-input",
1560
+ placeholder: t('transferToPlaceholder'),
1561
+ value: toDid,
1562
+ onChange: e => setToDid(e.target.value),
1563
+ disabled: loading
1564
+ })), /*#__PURE__*/React.createElement("div", {
1565
+ className: "points-form-group"
1566
+ }, /*#__PURE__*/React.createElement("label", null, t('transferAmount')), /*#__PURE__*/React.createElement("input", {
1567
+ type: "number",
1568
+ className: "points-input",
1569
+ placeholder: t('minAmountMsg', {
1570
+ min: minAmount
1571
+ }),
1572
+ value: amount,
1573
+ onChange: e => setAmount(e.target.value),
1574
+ min: minAmount,
1575
+ max: available,
1576
+ disabled: loading
1577
+ }), /*#__PURE__*/React.createElement("div", {
1578
+ className: "points-quick-amounts"
1579
+ }, /*#__PURE__*/React.createElement("button", {
1580
+ onClick: () => setQuickAmount(25),
1581
+ disabled: loading
1582
+ }, "25%"), /*#__PURE__*/React.createElement("button", {
1583
+ onClick: () => setQuickAmount(50),
1584
+ disabled: loading
1585
+ }, "50%"), /*#__PURE__*/React.createElement("button", {
1586
+ onClick: () => setQuickAmount(75),
1587
+ disabled: loading
1588
+ }, "75%"), /*#__PURE__*/React.createElement("button", {
1589
+ onClick: () => setQuickAmount(100),
1590
+ disabled: loading
1591
+ }, t('allAmount')))), /*#__PURE__*/React.createElement("div", {
1592
+ className: "points-form-group"
1593
+ }, /*#__PURE__*/React.createElement("label", null, t('messageOptional')), /*#__PURE__*/React.createElement("textarea", {
1594
+ className: "points-input",
1595
+ placeholder: t('messagePlaceholder'),
1596
+ value: message,
1597
+ onChange: e => setMessage(e.target.value),
1598
+ rows: 2,
1599
+ disabled: loading
1600
+ })), error && /*#__PURE__*/React.createElement("div", {
1601
+ className: "points-error"
1602
+ }, error), /*#__PURE__*/React.createElement("div", {
1603
+ className: "points-form-actions"
1604
+ }, onCancel && /*#__PURE__*/React.createElement("button", {
1605
+ className: "points-btn points-btn-secondary",
1606
+ onClick: onCancel,
1607
+ disabled: loading
1608
+ }, t('cancel')), /*#__PURE__*/React.createElement("button", {
1609
+ className: "points-btn points-btn-primary",
1610
+ onClick: handleSubmit,
1611
+ disabled: loading
1612
+ }, loading ? t('loading') : t('transfer')))));
1613
+ }
1614
+
1615
+ const POINTS_ANCHOR_NAMESPACE = 'qbit_points';
1616
+ function pickPointsId(points) {
1617
+ return points?.id || points?.points_id || points?.pointsId || points?.source_id || points?.sourceId || points?.transaction_id || points?.transactionId;
1618
+ }
1619
+ function pickContentHash(points, options) {
1620
+ return options.contentHash || points?.content_hash || points?.points_hash || points?.transaction_hash || points?.hash || null;
1621
+ }
1622
+ async function buildPointsAnchorMemo(points, options = {}) {
1623
+ const {
1624
+ buildQBitAnchorMemo
1625
+ } = await import('@quantabit/qbit-chain-sdk');
1626
+ const pointsId = options.pointsId || pickPointsId(points);
1627
+ return buildQBitAnchorMemo({
1628
+ action: options.action || 'points_anchor',
1629
+ subject: pointsId ? `points:${pointsId}` : 'points',
1630
+ resource_id: pointsId,
1631
+ content_hash: pickContentHash(points, options),
1632
+ version: options.version,
1633
+ timestamp: options.timestamp,
1634
+ extra: {
1635
+ amount: points?.amount,
1636
+ points_type: points?.points_type || points?.pointsType,
1637
+ user_did: points?.user_did || points?.userDid,
1638
+ source_platform: points?.source_platform || points?.sourcePlatform,
1639
+ change_type: points?.change_type || points?.changeType,
1640
+ ...options.extra
1641
+ }
1642
+ }, {
1643
+ namespace: options.namespace || POINTS_ANCHOR_NAMESPACE,
1644
+ maxBytes: options.maxBytes
1645
+ });
1646
+ }
1647
+ function buildPointsChainSubmit(pointsId, txHash, options = {}) {
1648
+ return {
1649
+ points_id: pointsId,
1650
+ tx_hash: txHash,
1651
+ chain: options.chain || 'qbit',
1652
+ network: options.network || 'mainnet',
1653
+ action: options.action || 'points_anchor',
1654
+ memo: options.memo,
1655
+ content_hash: options.contentHash,
1656
+ ...options.extra
1657
+ };
1658
+ }
1659
+
1660
+ /**
1661
+ * 将积分记录锚定到 QBit 链(构建 memo → 钱包签名广播 → 可选确认)
1662
+ *
1663
+ * @param {Object} provider - QBit 钱包 provider
1664
+ * @param {Object} points - 积分记录对象
1665
+ * @param {Object} options - 选项(endpoint, network, address, waitForConfirmation...)
1666
+ * @returns {Promise<{txHash: string|null, memo: string, confirmed?: boolean, slot?: number|null, submit: Object}>}
1667
+ */
1668
+ async function anchorPoints(provider, points, options = {}) {
1669
+ const sdk = await import('@quantabit/qbit-chain-sdk');
1670
+ const connection = new sdk.QBitConnection({
1671
+ endpoint: options.endpoint,
1672
+ network: options.network || 'mainnet'
1673
+ });
1674
+ const memo = await buildPointsAnchorMemo(points, options);
1675
+ const result = await sdk.broadcastQBitAnchor(connection, provider, memo, {
1676
+ address: options.address,
1677
+ priorityFee: options.priorityFee,
1678
+ skipPreflight: options.skipPreflight,
1679
+ waitForConfirmation: options.waitForConfirmation,
1680
+ commitment: options.commitment,
1681
+ timeoutMs: options.confirmTimeoutMs
1682
+ });
1683
+ const pointsId = options.pointsId || pickPointsId(points);
1684
+ return {
1685
+ ...result,
1686
+ submit: buildPointsChainSubmit(pointsId, result.txHash, {
1687
+ ...options,
1688
+ memo: result.memo,
1689
+ contentHash: pickContentHash(points, options)
1690
+ })
1691
+ };
1692
+ }
1693
+
1694
+ /**
1695
+ * 校验积分记录是否已成功锚定到链上
1696
+ *
1697
+ * @param {string} txHash - 交易签名
1698
+ * @param {Object} options - { endpoint, network, match }
1699
+ * @returns {Promise<{found: boolean, matched: boolean, memos: Array<Object>}>}
1700
+ */
1701
+ async function verifyPointsAnchor(txHash, options = {}) {
1702
+ const sdk = await import('@quantabit/qbit-chain-sdk');
1703
+ const connection = new sdk.QBitConnection({
1704
+ endpoint: options.endpoint,
1705
+ network: options.network || 'mainnet'
1706
+ });
1707
+ return sdk.verifyAnchoredMemo(connection, txHash, {
1708
+ namespace: options.namespace || POINTS_ANCHOR_NAMESPACE,
1709
+ match: options.match,
1710
+ commitment: options.commitment
1711
+ });
1712
+ }
1713
+
1714
+ /**
1715
+ * Points SDK - 高级组件: 积分概览、任务中心、兑换商城
1716
+ */
1717
+
1718
+
1719
+ // 积分概览卡片
1720
+ function PointsOverview({
1721
+ balance = 0,
1722
+ todayEarned = 0,
1723
+ monthlyEarned = 0,
1724
+ rank,
1725
+ level,
1726
+ nextLevelPoints,
1727
+ onHistoryClick,
1728
+ className = ''
1729
+ }) {
1730
+ const progress = nextLevelPoints ? Math.min(balance / nextLevelPoints * 100, 100) : 0;
1731
+ return /*#__PURE__*/React.createElement("div", {
1732
+ className: `points-overview ${className}`
1733
+ }, /*#__PURE__*/React.createElement("div", {
1734
+ className: "points-balance-section"
1735
+ }, /*#__PURE__*/React.createElement("div", {
1736
+ className: "points-balance-label"
1737
+ }, t('currentPoints')), /*#__PURE__*/React.createElement("div", {
1738
+ className: "points-balance-value"
1739
+ }, /*#__PURE__*/React.createElement("span", {
1740
+ className: "points-icon"
1741
+ }, "\u2B50"), balance.toLocaleString()), /*#__PURE__*/React.createElement("div", {
1742
+ className: "points-balance-stats"
1743
+ }, /*#__PURE__*/React.createElement("span", null, t('todayEarned'), ": +", todayEarned), /*#__PURE__*/React.createElement("span", null, "\xB7"), /*#__PURE__*/React.createElement("span", null, t('monthlyEarned'), ": +", monthlyEarned))), level && /*#__PURE__*/React.createElement("div", {
1744
+ className: "points-level-section"
1745
+ }, /*#__PURE__*/React.createElement("div", {
1746
+ className: "points-level-header"
1747
+ }, /*#__PURE__*/React.createElement("span", {
1748
+ className: "points-level-badge"
1749
+ }, "Lv.", level.current), /*#__PURE__*/React.createElement("span", {
1750
+ className: "points-level-name"
1751
+ }, level.name)), /*#__PURE__*/React.createElement("div", {
1752
+ className: "points-level-progress"
1753
+ }, /*#__PURE__*/React.createElement("div", {
1754
+ className: "points-level-bar",
1755
+ style: {
1756
+ width: `${progress}%`
1757
+ }
1758
+ })), /*#__PURE__*/React.createElement("div", {
1759
+ className: "points-level-text"
1760
+ }, "\u8DDD\u79BB Lv.", level.current + 1, " \u8FD8\u9700 ", (nextLevelPoints - balance).toLocaleString(), " \u79EF\u5206")), rank && /*#__PURE__*/React.createElement("div", {
1761
+ className: "points-rank-section"
1762
+ }, /*#__PURE__*/React.createElement("span", {
1763
+ className: "points-rank-icon"
1764
+ }, "\uD83C\uDFC6"), /*#__PURE__*/React.createElement("span", null, "\u6392\u540D #", rank)), /*#__PURE__*/React.createElement("button", {
1765
+ className: "points-btn points-btn-outline",
1766
+ onClick: onHistoryClick
1767
+ }, t('viewHistory'), " \u2192"));
1768
+ }
1769
+
1770
+ // 积分任务列表
1771
+ function PointsTasks({
1772
+ tasks = [],
1773
+ loading = false,
1774
+ onTaskClick,
1775
+ onTaskComplete,
1776
+ className = ''
1777
+ }) {
1778
+ const taskTypeIcons = {
1779
+ daily: '📅',
1780
+ weekly: '📆',
1781
+ onetime: '🎯',
1782
+ achievement: '🏆'
1783
+ };
1784
+ if (loading) {
1785
+ return /*#__PURE__*/React.createElement("div", {
1786
+ className: `points-tasks ${className}`
1787
+ }, /*#__PURE__*/React.createElement("div", {
1788
+ className: "points-loading"
1789
+ }, /*#__PURE__*/React.createElement("div", {
1790
+ className: "points-spinner"
1791
+ })));
1792
+ }
1793
+ if (tasks.length === 0) {
1794
+ return /*#__PURE__*/React.createElement("div", {
1795
+ className: `points-tasks ${className}`
1796
+ }, /*#__PURE__*/React.createElement("div", {
1797
+ className: "points-empty"
1798
+ }, /*#__PURE__*/React.createElement("span", {
1799
+ style: {
1800
+ fontSize: '48px'
1801
+ }
1802
+ }, "\u2705"), /*#__PURE__*/React.createElement("span", null, t('allTasksCompleted'))));
1803
+ }
1804
+ return /*#__PURE__*/React.createElement("div", {
1805
+ className: `points-tasks ${className}`
1806
+ }, tasks.map(task => /*#__PURE__*/React.createElement("div", {
1807
+ key: task.id,
1808
+ className: `points-task-item ${task.completed ? 'completed' : ''}`,
1809
+ onClick: () => onTaskClick?.(task)
1810
+ }, /*#__PURE__*/React.createElement("div", {
1811
+ className: "points-task-icon"
1812
+ }, taskTypeIcons[task.type] || '📋'), /*#__PURE__*/React.createElement("div", {
1813
+ className: "points-task-content"
1814
+ }, /*#__PURE__*/React.createElement("div", {
1815
+ className: "points-task-title"
1816
+ }, task.title), /*#__PURE__*/React.createElement("div", {
1817
+ className: "points-task-desc"
1818
+ }, task.description), task.progress !== undefined && !task.completed && /*#__PURE__*/React.createElement("div", {
1819
+ className: "points-task-progress"
1820
+ }, /*#__PURE__*/React.createElement("div", {
1821
+ className: "points-task-progress-bar"
1822
+ }, /*#__PURE__*/React.createElement("div", {
1823
+ style: {
1824
+ width: `${task.progress / task.target * 100}%`
1825
+ }
1826
+ })), /*#__PURE__*/React.createElement("span", {
1827
+ className: "points-task-progress-text"
1828
+ }, task.progress, "/", task.target))), /*#__PURE__*/React.createElement("div", {
1829
+ className: "points-task-reward"
1830
+ }, /*#__PURE__*/React.createElement("span", {
1831
+ className: "points-reward-icon"
1832
+ }, "\u2B50"), /*#__PURE__*/React.createElement("span", {
1833
+ className: "points-reward-value"
1834
+ }, "+", task.reward)), !task.completed && task.progress >= task.target && /*#__PURE__*/React.createElement("button", {
1835
+ className: "points-btn points-btn-sm points-btn-primary",
1836
+ onClick: e => {
1837
+ e.stopPropagation();
1838
+ onTaskComplete?.(task.id);
1839
+ }
1840
+ }, t('claim')), task.completed && /*#__PURE__*/React.createElement("span", {
1841
+ className: "points-task-completed-badge"
1842
+ }, "\u2713 ", t('completed')))));
1843
+ }
1844
+
1845
+ // 积分兑换商城
1846
+ function PointsShop({
1847
+ products = [],
1848
+ userPoints = 0,
1849
+ loading = false,
1850
+ onProductClick,
1851
+ onExchange,
1852
+ className = ''
1853
+ }) {
1854
+ if (loading) {
1855
+ return /*#__PURE__*/React.createElement("div", {
1856
+ className: `points-shop ${className}`
1857
+ }, /*#__PURE__*/React.createElement("div", {
1858
+ className: "points-loading"
1859
+ }, /*#__PURE__*/React.createElement("div", {
1860
+ className: "points-spinner"
1861
+ })));
1862
+ }
1863
+ if (products.length === 0) {
1864
+ return /*#__PURE__*/React.createElement("div", {
1865
+ className: `points-shop ${className}`
1866
+ }, /*#__PURE__*/React.createElement("div", {
1867
+ className: "points-empty"
1868
+ }, /*#__PURE__*/React.createElement("span", {
1869
+ style: {
1870
+ fontSize: '48px'
1871
+ }
1872
+ }, "\uD83C\uDF81"), /*#__PURE__*/React.createElement("span", null, t('noProducts'))));
1873
+ }
1874
+ return /*#__PURE__*/React.createElement("div", {
1875
+ className: `points-shop ${className}`
1876
+ }, /*#__PURE__*/React.createElement("div", {
1877
+ className: "points-shop-grid"
1878
+ }, products.map(product => {
1879
+ const canAfford = userPoints >= product.price;
1880
+ const soldOut = product.stock <= 0;
1881
+ return /*#__PURE__*/React.createElement("div", {
1882
+ key: product.id,
1883
+ className: `points-product-card ${!canAfford ? 'unaffordable' : ''} ${soldOut ? 'sold-out' : ''}`,
1884
+ onClick: () => onProductClick?.(product)
1885
+ }, /*#__PURE__*/React.createElement("div", {
1886
+ className: "points-product-image"
1887
+ }, product.image ? /*#__PURE__*/React.createElement("img", {
1888
+ src: product.image,
1889
+ alt: product.name
1890
+ }) : /*#__PURE__*/React.createElement("span", {
1891
+ className: "points-product-placeholder"
1892
+ }, "\uD83C\uDF81"), soldOut && /*#__PURE__*/React.createElement("div", {
1893
+ className: "points-product-badge sold-out"
1894
+ }, t('soldOut')), product.hot && !soldOut && /*#__PURE__*/React.createElement("div", {
1895
+ className: "points-product-badge hot"
1896
+ }, "\uD83D\uDD25 ", t('hot'))), /*#__PURE__*/React.createElement("div", {
1897
+ className: "points-product-info"
1898
+ }, /*#__PURE__*/React.createElement("div", {
1899
+ className: "points-product-name"
1900
+ }, product.name), /*#__PURE__*/React.createElement("div", {
1901
+ className: "points-product-desc"
1902
+ }, product.description)), /*#__PURE__*/React.createElement("div", {
1903
+ className: "points-product-footer"
1904
+ }, /*#__PURE__*/React.createElement("div", {
1905
+ className: "points-product-price"
1906
+ }, /*#__PURE__*/React.createElement("span", {
1907
+ className: "points-icon"
1908
+ }, "\u2B50"), product.price.toLocaleString()), /*#__PURE__*/React.createElement("button", {
1909
+ className: "points-btn points-btn-sm points-btn-primary",
1910
+ disabled: !canAfford || soldOut,
1911
+ onClick: e => {
1912
+ e.stopPropagation();
1913
+ onExchange?.(product);
1914
+ }
1915
+ }, soldOut ? t('soldOut') : !canAfford ? t('insufficientPoints') : t('exchange'))), product.stock > 0 && product.stock <= 10 && /*#__PURE__*/React.createElement("div", {
1916
+ className: "points-product-stock"
1917
+ }, t('onlyLeft').replace('{count}', product.stock)));
1918
+ })));
1919
+ }
1920
+
1921
+ // 积分历史
1922
+ function PointsHistory({
1923
+ records = [],
1924
+ loading = false,
1925
+ onLoadMore,
1926
+ hasMore = false,
1927
+ className = ''
1928
+ }) {
1929
+ if (loading && records.length === 0) {
1930
+ return /*#__PURE__*/React.createElement("div", {
1931
+ className: `points-history ${className}`
1932
+ }, /*#__PURE__*/React.createElement("div", {
1933
+ className: "points-loading"
1934
+ }, /*#__PURE__*/React.createElement("div", {
1935
+ className: "points-spinner"
1936
+ })));
1937
+ }
1938
+ if (records.length === 0) {
1939
+ return /*#__PURE__*/React.createElement("div", {
1940
+ className: `points-history ${className}`
1941
+ }, /*#__PURE__*/React.createElement("div", {
1942
+ className: "points-empty"
1943
+ }, /*#__PURE__*/React.createElement("span", {
1944
+ style: {
1945
+ fontSize: '48px'
1946
+ }
1947
+ }, "\uD83D\uDCCB"), /*#__PURE__*/React.createElement("span", null, t('noHistory'))));
1948
+ }
1949
+ return /*#__PURE__*/React.createElement("div", {
1950
+ className: `points-history ${className}`
1951
+ }, records.map((record, index) => /*#__PURE__*/React.createElement("div", {
1952
+ key: record.id || index,
1953
+ className: "points-history-item"
1954
+ }, /*#__PURE__*/React.createElement("div", {
1955
+ className: "points-history-icon"
1956
+ }, record.amount > 0 ? '📥' : '📤'), /*#__PURE__*/React.createElement("div", {
1957
+ className: "points-history-content"
1958
+ }, /*#__PURE__*/React.createElement("div", {
1959
+ className: "points-history-title"
1960
+ }, record.title), /*#__PURE__*/React.createElement("div", {
1961
+ className: "points-history-time"
1962
+ }, new Date(record.created_at).toLocaleString())), /*#__PURE__*/React.createElement("div", {
1963
+ className: `points-history-amount ${record.amount > 0 ? 'positive' : 'negative'}`
1964
+ }, record.amount > 0 ? '+' : '', record.amount.toLocaleString()))), hasMore && /*#__PURE__*/React.createElement("button", {
1965
+ className: "points-btn points-btn-outline",
1966
+ onClick: onLoadMore,
1967
+ disabled: loading,
1968
+ style: {
1969
+ width: '100%',
1970
+ marginTop: '16px'
1971
+ }
1972
+ }, loading ? t('loading') : t('loadMore')));
1973
+ }
1974
+
1975
+ exports.CheckInCalendar = CheckInCalendar;
1976
+ exports.POINTS_ANCHOR_NAMESPACE = POINTS_ANCHOR_NAMESPACE;
1977
+ exports.PointConsume = PointConsume;
1978
+ exports.PointSource = PointSource;
1979
+ exports.PointStatus = PointStatus;
1980
+ exports.PointsApiClient = PointsApiClient;
1981
+ exports.PointsBalance = PointsBalance;
1982
+ exports.PointsHistory = PointsHistory$1;
1983
+ exports.PointsHistoryAdvanced = PointsHistory;
1984
+ exports.PointsOverview = PointsOverview;
1985
+ exports.PointsProvider = PointsProvider;
1986
+ exports.PointsShop = PointsShop;
1987
+ exports.PointsTasks = PointsTasks;
1988
+ exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES;
1989
+ exports.TransactionType = TransactionType;
1990
+ exports.TransferPoints = TransferPoints;
1991
+ exports.anchorPoints = anchorPoints;
1992
+ exports.buildPointsAnchorMemo = buildPointsAnchorMemo;
1993
+ exports.buildPointsChainSubmit = buildPointsChainSubmit;
1994
+ exports.createDefaultCheckInConfig = createDefaultCheckInConfig;
1995
+ exports.createDefaultExchangeRule = createDefaultExchangeRule;
1996
+ exports.createDefaultPointsAccount = createDefaultPointsAccount;
1997
+ exports.createDefaultPointsTransaction = createDefaultPointsTransaction;
1998
+ exports.formatPoints = formatPoints;
1999
+ exports.getLanguage = getLanguage;
2000
+ exports.messages = messages;
2001
+ exports.pointsApi = pointsApi;
2002
+ exports.setLanguage = setLanguage;
2003
+ exports.t = t;
2004
+ exports.usePoints = usePoints;
2005
+ exports.verifyPointsAnchor = verifyPointsAnchor;
2006
+ //# sourceMappingURL=index.cjs.map